Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous partial specialization depending on std::enable_if

Tags:

c++

templates

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.

like image 783
R_Kapp Avatar asked Oct 29 '22 11:10

R_Kapp


1 Answers

In the std::enable_if<true>::type case your code defines two specialisations of the class A namely:

  1. A<B<T>, void>
  2. 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.

  1. A<B<T>, void>
  2. 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

like image 105
Frank Boyne Avatar answered Nov 15 '22 07:11

Frank Boyne