I have the following code:
#include <iostream>
template <class T, typename U = void> class A;
template <class T>
class C
{
public:
typedef T Var_t;
};
template <class T>
class B : public C<T>
{
};
template <class T>
class A<B<T>>
{
public:
A() { std::cout << "Here." << std::endl; }
};
template <class T>
class A<T, typename std::enable_if<
std::is_base_of<C<typename T::Var_t>, T>::value>
::type>
{
public:
A() { std::cout << "There." << std::endl;}
};
int main()
{
A<B<int>> a;
return 0;
}
When the compiler tries to instantiate the second partial specialization with the parameter B<int>
, std::is_base_of<C<int>, B<int>>::value
is true
, and therefore the std::enable_if<...>::type
returns void
(the default type if one isn't specified). This causes an "ambiguous partial specialization" error as the compiler can't decide between the first and second partial specializations. So far, so good. However, when I replace the code within the std::enable_if
to simply be true
(i.e., the second partial specialization is just template <class T> class A<T, typename std::enable_if<true>::type>
), the code compiles and runs. It outputs "Here"
, indicating the first specialization was chosen.
My question is: If they both evaluate to void
in the end, why does the behavior of std::enable_if<true>::type
differ to that of std::enable_if<std::is_base_of<...>::value>::type
?
This behavior has been tested and verified on Ideone here.
In the std::enable_if<true>::type
case your code defines two specialisations of the class A namely:
A<B<T>, void>
A<T, std::enable_if<true>::type>
. Those two specialisations are quite distinct from each other. The first specialisation is narrowly focused on the type B<T>
while the second specialisation is more general fitting any type at all. Also, in the second specialisation the std::enable_if
expression does not depend on T
in any way.
For any declaration A<X> a;
the type X
will either match B<something>
or not. If it matches B<something>
then the first specialisation will be used because it is "more specialised". If X does not match B<something>
then the second, more general specialisation will be used. Either way you don't get the ambiguous error.
For more details see the discussion of Partial Ordering in partial template specialization
Now let's consider the std::enable_if<std::is_base_of<...>::value>::type
case.
You still have two specialisations but the second specialisation is now conditional on the enable_if which in turn depends on the parameter T.
A<B<T>, void>
A<T, std::enable_if<...>>
. The type B<int>
now matches both specialisations (to some equal extent). Obviously it matches the A<B<T>>, void>
specialisation but it also matches the A<T, std::enable_if...>>
specialisation because B<int>
is a type which satisfies the conditions imposed by the std::enable_if
expression.
That gives you two equally valid specialisations that are candidates for your declaration of the variable a
and so you get the "ambiguous partial specialization" error.
It might help make all this a bit more concrete if you added two more declarations to main
A<C<int>> x;
A<int> y;
In the std::enable_if<true>
case this will compile and both declarations will call the "there" constructor.
In the more complex case the declaration of x
will compile and invoke the "there" constructor but the declaration of y
will get a compiler error.
There is no int::Var_t
so the std::enable_if
expression will get a substitution failure and SFINAE will hide that specialisation. That means there won't be any specialisation that fits int
and you'll get the error aggregate ‘A<int> y’ has incomplete type and cannot be defined
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With