(Partially) specializing a non-type template parameter of dependent type
Asked Answered
B

4

34

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?

Belindabelisarius answered 18/3, 2014 at 17:13 Comment(0)
C
33

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>> {};
Conduplicate answered 18/3, 2014 at 17:24 Comment(12)
Thanks. I've found a workaround (see edited question) - any better idea?Belindabelisarius
@Belindabelisarius I've edited my answer to give the solution for your edit.Conduplicate
Great, I've combined this with my first attempt, see edited answer.Belindabelisarius
@Belindabelisarius I don't understand how std::integral_constant<T, N> can prevent the use of N in computations.Conduplicate
Oops, you're right. I was thinking of the typename T = std::integral_constant<T, N> 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 :-)Belindabelisarius
@Belindabelisarius You may also replace common (non-specialized) case with additional specialization: template <typename T, T N> struct X_<T, std::integral_constant<T, N>>;. So you can simply use N in such 'common' case.Conduplicate
@Belindabelisarius I've added the code from my previous comment to my answer.Conduplicate
I can't explain why, but GCC cannot disambiguate between the general definition and the specialization in your solution. Clang works fine.Belindabelisarius
@Belindabelisarius It is strange. But note that X <int, 0>(); is not what you possibly want.Conduplicate
@Belindabelisarius gcc accepts it if you cast 0 to T in the specialization: coliru.stacked-crooked.com/a/435ff04ca4640ae8Cretic
Can someone explain what the motivation behind this paragraph in the standard is? What good does it do?Compression
@NikTedig Basically such paragraphs are needed to simplify the development of compilers and/or to avoid ambiguities during the parsing of a source code.Conduplicate
S
12

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

Sepoy answered 18/3, 2014 at 17:47 Comment(4)
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.Belindabelisarius
@Belindabelisarius Your solution is very slick, and I'd probably stick with it if I were you (with a comment of course!)Sepoy
@Belindabelisarius 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.Sepoy
Right, I'm working with numbers here. But enable_if is certainly more generic.Belindabelisarius
E
5

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.

Endoscope answered 18/3, 2014 at 17:32 Comment(1)
unrelated but you may find this interesting. I'd very much value your thoughts on this: #38964636Dumbwaiter
O
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 {};
Oscillograph answered 18/3, 2014 at 18:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.