std::enable_if_t doesn't match partial specialization

71 Views Asked by At

If my understanding is correct, alias templates are actually templates and hence can be passed as arguments where a class template is expected. Which is what makes the following code valid -

template <template <bool, typename> T>
struct foo {};

foo<std::enable_if_t> x;

It is also possible to use partial specialization to match template classes. Which is why the following code prints "Version 2".

template <typename T>
struct foo {
    static void print() {std::cout << "Version 1" << std::endl;}
};

template <template <typename> typename T, typename X>
struct foo<T<X>> {
    static void print() {std::cout << "Version 2" << std::endl;}
};

template <typename T>
struct bar {

};
...
foo<bar<int>>::print();

Now my question is, why doesn't the following code print "Version 2", but instead prints "Version 1"

template <typename T>
struct foo {
    static void print() {std::cout << "Version 1" << std::endl;}    
};


template <template <bool, typename> typename T, bool X, typename Y>
struct foo<T<X, Y>> {
    static void print() {std::cout << "Version 2" << std::endl;}
};

foo<std::enable_if_t<true, int>>::print();

Shouldn't the second partial specialization be a better match where T=std::enable_if_t, X=true, Y=int?

2

There are 2 best solutions below

2
Etienne Laurin On

As expected, the following prints "Version 2"

foo<std::enable_if<true, int>>::print();

In your example however, the type std::enable_if_t<true, int> is equivalent to int and cannot match cannot match T<X, Y>.

cppreference explains it like this:

An alias template is a template which, when specialized, is equivalent to the result of substituting the template arguments of the alias template for the template parameters in the type-id

The alias std::enable_if_t can match template <bool, typename> typename, but once specialized it cannot be deconstructed.

2
Brian Bi On

Whenever an alias template has arguments, it is immediately replaced with the type it aliases. So, for example, std::enable_if_t<true, int> is immediately replaced by int. This applies even in a dependent context; so for example if you have a function like this:

template <bool b, typename T>
void foo(std::enable_if_t<b, T>);

the compiler rewrites it to:

template <bool b, typename T>
void foo(typename std::enable_if<b, T>::type);

The original alias has disappeared from the equation.

It follows that a type like T<X, Y> can't be used to deduce T as an alias template because whatever type U is provided for T<X, Y> to match against, if it originally had the form A<X, Y> for some alias template A, it would have been replaced by whatever that alias template aliases to, and A would no longer be present. (However, an alias template can be deduced as an argument of a class template that has a template template parameter, since an alias template can be a valid template template argument)