29

Maybe I'm tired, but I'm stuck with this simple partial specialization, which doesn't work because non-type template argument specializes a template parameter with dependent type 'T':

template <typename T, T N> struct X;
template <typename T>      struct X <T, 0>;

Replacing 0 by T(0), T{0} or (T)0 doesn't help. So is this specialization even possible?

Barry
  • 247,587
  • 26
  • 487
  • 819
iavr
  • 7,277
  • 1
  • 13
  • 50

4 Answers4

27

See paragraph [temp.class.spec] 14.5.5/8 of the standard:

The type of a template parameter corresponding to a specialized non-type argument shall not be dependent on a parameter of the specialization. [ Example:

template <class T, T t> struct C {};
template <class T> struct C<T, 1>; // error

template< int X, int (*array_ptr)[X] > class A {};
int array[5];
template< int X > class A<X,&array> { }; // error

—end example ]

The answer to your edit: the easiest workaround is to replace a non-type template parameter with a type one:

#include <type_traits>

template <typename T, typename U>
struct X_;

template <typename T, T N>
struct X_<T, std::integral_constant<T, N>> {};

template <typename T>
struct X_<T, std::integral_constant<T, 0>> {};

template <typename T, T N>
struct X : X_<T, std::integral_constant<T, N>> {};
Constructor
  • 6,903
  • 2
  • 17
  • 55
  • Thanks. I've found a workaround (see edited question) - any better idea? – iavr Mar 18 '14 at 17:26
  • @iavr I've edited my answer to give the solution for your edit. – Constructor Mar 18 '14 at 17:37
  • Great, I've combined this with my first attempt, see edited answer. – iavr Mar 18 '14 at 19:05
  • @iavr I don't understand how `std::integral_constant` can prevent the use of `N` in computations. – Constructor Mar 18 '14 at 19:13
  • Oops, you're right. I was thinking of the `typename T = std::integral_constant` itself, as a type (so I would use it as `T{}` or `T::value`). But `N` is also available! Anyhow, I may keep it like this just for aesthetic reasons :-) – iavr Mar 18 '14 at 19:31
  • @iavr You may also replace common (non-specialized) case with additional specialization: `template struct X_>;`. So you can simply use `N` in such 'common' case. – Constructor Mar 18 '14 at 19:40
  • @iavr I've added the code from my previous comment to my answer. – Constructor Mar 18 '14 at 19:44
  • 1
    I can't explain why, but [GCC cannot disambiguate](http://coliru.stacked-crooked.com/a/4866f492540ecd63) between the general definition and the specialization in your solution. Clang works fine. – iavr Mar 18 '14 at 20:53
  • @iavr It is strange. But note that `X ();` is not what you possibly want. – Constructor Mar 18 '14 at 21:04
  • 2
    @iavr gcc accepts it if you cast `0` to `T` in the specialization: http://coliru.stacked-crooked.com/a/435ff04ca4640ae8 – ecatmur Jan 22 '16 at 16:13
10

Solution using Yakk's solution:

#include <iostream>
#include <type_traits>

template <typename T, T N, typename = void > 
struct X {
  static const bool isZero = false;
};

template <typename T, T N>
struct X < T, N, typename std::enable_if<N == 0>::type > {
  static const bool isZero = true;
};

int main(int argc, char* argv[]) {
    std::cout << X <int, 0>::isZero << std::endl;
    std::cout << X <int, 1>::isZero << std::endl;
    return 0;
}

Live Demo

Sam Cristall
  • 4,170
  • 14
  • 27
  • 2
    Interesting, I haven't used `enable_if` this way before. I've chosen a different approach here (see edit), but I will keep this in mind because I guess it is more generic in checking arbitrary conditions on one or more of the template arguments. – iavr Mar 18 '14 at 19:09
  • @iavr Your solution is very slick, and I'd probably stick with it if I were you (with a comment of course!) – Sam Cristall Mar 18 '14 at 19:19
  • @iavr For what it's worth, my solution works with pointers, whereas your EDIT 2 solution does not. But you probably don't need that functionality. – Sam Cristall Mar 18 '14 at 19:27
  • Right, I'm working with numbers here. But `enable_if` is certainly more generic. – iavr Mar 18 '14 at 19:34
3

You can add a typename=void parameter to the end of the list of template arguments, then go hog wild with std::enable_if_t< condition > in specializations.

Yakk - Adam Nevraumont
  • 235,777
  • 25
  • 285
  • 465
  • unrelated but you may find this interesting. I'd very much value your thoughts on this: http://stackoverflow.com/questions/38964136/c-handle-body-class-automatically-composed-from-template-parameter-pack-can-t – Richard Hodges Aug 16 '16 at 09:46
0

You need to pass an integral value in a template, Both, your first and second template, will not work if the type T is not an integral type.

You can pass Traits as a typed template parameter to specify the value N:

#include <iostream>

// error: ‘double’ is not a valid type for a template non-type parameter
template <typename T, T N> struct X0;

// error: ‘double’ is not a valid type for a template non-type parameter
template <typename T, T N, int = 0> struct X1;



template <typename T, T N>
struct IntegralTraits {
    static constexpr T Value() { return N; }
};

template <typename T, typename Traits = void>
struct X2 {
    static constexpr T Value() { return Traits::Value(); }
};

template <typename T>
struct X2<T, void> {
    static constexpr T Value() { return T(); }
};


int main() {
    // error: ‘double’ is not a valid type for a template non-type parameter
    // X0<double, 0>();

    // error: ‘double’ is not a valid type for a template non-type parameter
    // X1<double, 0>();

    X2<int> a;
    X2<double, IntegralTraits<int, 1>> b;

    std::cout.precision(2);
    std::cout << std::fixed  <<  a.Value() << ", "<< b.Value() << '\n';
    return 0;
}

If you limit yourself to integral types pick a large one:

template <typename T, std::size_t N = 0> struct X {};