Search code examples
c++templatesmetaprogrammingtemplate-templatesnon-type

Using non-type template template parameter in template specialisation


I am learning about template metaprogramming and I have hit a snag trying to achieve something which I think should be possible, I just quite can't figure out how to do it.

I found these two answers that I thought were somewhat related but didn't really help

Let's say I want to represent points in 1D and 2D space, then I can define these as follows:

template <int X>
struct Point1D {
    static constexpr int x = X;
};

template <int X, int Y>
struct Point2D {
    static constexpr int x = X;
    static constexpr int y = Y;
};

Now I want to calculate the distance between two points. One option is to do this:

template <typename Point1, typename Point2>
struct Distance {
    static constexpr int value = Point2::x - Point1::x;
};

typedef Distance<Point1D<1>, Point1D<5>> dist;
dist::value; //4

However this will accept both Point2D and ANY type that defines ::x

template <int X>
struct RandomType {
    static constexpr int x = X;
};

typedef Distance<Point2D<5, 4>, Point1D<2>> dist2; //Should not work
typedef Distance<Point1D<5>, RandomType<6>> dist3; //Should not work either

Now, it should be possible to specialise Distance for Point1D and Point2D types, however this is where I get stuck. I tried the following:

template <template <int...> typename Point1, template <int...> typename Point2>
struct Distance {};

template <>
struct Distance<Point1D, Point1D> {
    /* Calculate distance between two points */
};

template <>
struct Distance<Point2D, Point2D> {
    /* Calculate distance between two points in 2D space */
};

But of course now I am specialising for a class template, not a type, so this won't work. I can pass the non-type parameters to the specialisation as such:

template <template <int...> typename Point1, int N, 
          template <int...> typename Point2, int M>
struct Distance {};

template <int N, int M>
struct Distance<Point1D, N, Point1D, M> {
    static constexpr int value = Point1D<M>::x - Point1D<N>::x;
};

typedef Distance<Point1D, 2, Point1D, 5> Two;
int x = Two::value; //3

But this defeats the purpose as I could just pass 5 and 2 for the distance. What I really need to do is able to pass any class template that takes one int N and has a member X so that I can call Distance with any Point1D<N>. E.g. something like this:

typedef Point1D<4> p1;
typedef Point1D<6> p2;
Distance<p1, p2>::value; //2

typedef Point2D<4, 6> p3;
Distance<p3, p1>::value //Error, can't mix Point1D and Point2D

/* Something of the sort (doesn't compile) */
template<int N, int M>
struct Distance<Point1D<N>, Point1D<M>> {
    static constexpr int value = Point1D<N>::x - Point1D<M>::x;
};

Conceptually I think this should work since at the point of the call to Distance the type contains all the information needed. Somehow, by specifying that Distance takes a template Point1D<int N> I need the function to be able to call Point1D<N>::x. The syntax is what I am unable to figure out.


Solution

  • If I understand the question correctly, the solution is to declare the template type Distance but not define it, and only partially specialize for each acceptable pair of types. For example:

    template <typename, typename>
    struct Distance;
    

    Since there is no definition for this template type, attempting to instantiate it with arbitrary types will fail, except when the type arguments match a partial specialization defined later.

    Now partially specialize for Point1D:

    template <int ...A, int ...B>
    struct Distance<Point1D<A...>, Point1D<B...>> {
        static constexpr int value = Point1D<B...>::x - Point1D<A...>::x;
    };
    

    You can partially specialize this for Point2D by using the same technique and changing the calculation as desired:

    template <int ...A, int ...B>
    struct Distance<Point2D<A...>, Point2D<B...>> {
        static constexpr int value = /* ... */;
    };
    

    (Note that the int parameter packs are technically unnecessary since you know how many parameters each type uses. However, using packs keeps the partial specialization definitions consistent with each other and makes the code simpler to maintain. It also enforces that the calculation uses the values Point_D<...>::x instead of just using the value of the template argument.)