Gilles Gilles - 3 months ago 27
C++ Question

SFINAE in class template constructors

I'm trying to make somthing with templates and SFINAE, in which I'm a beginner. I'm wasting a huge amount of time to make work every simplest thing. Can you help me understand how it works ?

The constructor of C< T , Ts... > takes a T parameter which is either a A< U > or a B< U >, but has a different behaviour in these two cases. I can't show you all I tried to do so. Here is the way that seemed to me the least stupid.

template<typename T> class A{
public: A(){} };

template<typename T> class B{
public: B(){} };

template<typename T> struct enable_if_A {};
template<typename T> struct enable_if_A< A<T> > {typedef A<T> type;};

template<typename T> struct enable_if_B {};
template<typename T> struct enable_if_B< B<T> > {typedef B<T> type;};

template<typename T,typename... Ts> class C{
public:
C(typename enable_if_A<T>::type const &p){cout << "A" << endl;}
C(typename enable_if_B<T>::type const &p){cout << "B" << endl;}
};

// ...

A<float> a;
B<float> b;

C<A<float> > ca(a); // error: no type named ‘type’ in ‘struct enable_if_B<A<float> >'
C<B<float> > cb(b); // error: no type named ‘type’ in ‘struct enable_if_A<B<float> >'


Note : I'm using g++ (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1. Should I upgrade it ?

Thanks

Edit : for more details, I also tried (inter alia) :

template<typename T,typename... Ts> class C{
public:
template<>
C(typename enable_if_A<T>::type const &p){cout << "A" << endl;}
template<>
C(typename enable_if_B<T>::type const &p){cout << "B" << endl;}
};
//explicit specialization in non-namespace scope ‘class bcifs::C<T, Ts>’

//////////////////////////////////////////////////////////////////////////

template<typename T,typename... Ts> class C{
public:
template<typename E=void>
C(typename enable_if_A<T>::type const &p){cout << "A" << endl;}
template<typename E=void>
C(typename enable_if_B<T>::type const &p){cout << "B" << endl;}
};
// error: no type named ‘type’ in ‘struct enable_if_B<A<float> >'

//////////////////////////////////////////////////////////////////////////

template<typename T> struct enable_if_A {};
template<typename T> struct enable_if_A< A<T> > {typedef void type;};

template<typename T> struct enable_if_B {};
template<typename T> struct enable_if_B< B<T> > {typedef void type;};

template<typename T,typename... Ts> class C{
public:
template<typename E=void>
C(T const &p);

C<typename enable_if_A<T>::type>(T const &p){cout << "A" << endl;}
C<typename enable_if_B<T>::type>(T const &p){cout << "B" << endl;}
};
// error: invalid declarator before ‘(’ token

//////////////////////////////////////////////////////////////////////////

template<typename T> class C{
public:
template<>
C(T const &p,typename enable_if_A<T>::type * = 0){cout << "A" << endl;}
template<>
C(T const &p,typename enable_if_B<T>::type * = 0){cout << "B" << endl;}
};
// error: explicit specialization in non-namespace scope ‘class C<T>’
// error: no type named ‘type’ in ‘struct enable_if_B<A<float> >’

//////////////////////////////////////////////////////////////////////////

template<typename T> class C{
public:
template<typename U>
C(T const &p,typename enable_if_A<T>::type * = 0){cout << "A" << endl;}
template<typename U>
C(T const &p,typename enable_if_B<T>::type * = 0){cout << "B" << endl;}
};
// error: no type named ‘type’ in ‘struct enable_if_B<A<float> >’
// error: no matching function for call to ‘C<A<float> >::C(A<float>&)’

//////////////////////////////////////////////////////////////////////////

template<typename T> struct enable_if_A {};
template<typename T> struct enable_if_A< A<T> > {typedef void type;};

template<typename T> struct enable_if_B {};
template<typename T> struct enable_if_B< B<T> > {typedef void type;};

template<typename T> class C{
public:
template <typename U>
C(A<U> const & r, void* _ = 0);
};

template <typename T>
template <typename U>
C<T>::C<T>(A<U> const & r, typename enable_if_A<U>::type* _ = 0) {
cout << "A" << endl;
}
// error: ISO C++ forbids declaration of ‘C’ with no type [-fpermissive]
// error: function template partial specialization ‘C<T>’ is not allowed
// error: no ‘int C<T>::C(const A<U>&, typename enable_if_A<U>::type*)’ member function declared in class ‘C<T>’
// C<T>::C<U>(... does the same


I'm sorry but I never managed to run your solutions. I finally found:

// dummy-function-parameter-ed version :

template<typename T> class C{
public:
template <typename U>
C(A<U> const &r,typename enable_if<is_same<A<U>,T>::value>::type* = 0){cout << "A" << endl;}

template <typename U>
C(B<U> const &r,typename enable_if<is_same<B<U>,T>::value>::type* = 0){cout << "B" << endl;}
};

// and the dummy-template-parameter-ed version :

template<typename T> class C{
public:
template<typename U,typename E = typename enable_if<is_same<A<U>,T>::value>::type>
C(A<U> &r){cout << "A" << endl;}

template<typename U,typename E = typename enable_if<is_same<B<U>,T>::value>::type>
C(B<U> &r){cout << "B" << endl;}
};

Answer
template<typename T,typename... Ts> class C{
public:
    C(typename enable_if_A<T>::type const &p){cout << "A" << endl;}
    C(typename enable_if_B<T>::type const &p){cout << "B" << endl;}
};

This is wrong, but you already knew that :) The reason is that SFINAE can only be applied when resolving overloads of templates, but you are trying to apply it to a member of a template. That is, SFINAE in your template above can only be applied to different C<T> types, but not the constructors of the C<T>.

To be able to apply SFINAE to the constructor, you need to make the constructor a template. But in your case that will lead to another limitation. Constructors are special functions for which you cannot provide a template argument (even if the constructor is templated), which means that the template type must be deduced from the place of call. But nested types are not deducible...

You can work around the limitation by changing the signature of the constructor:

template <typename T>
template <typename U>
C<T>::C<U>(A<U> const & r, typename enable_if_A<U>::type* _ = 0) {
    // ...
}

In this case the class C is a template that has a templated constructor taking a A<U>, which can only be used for types for which enable_if_A<U>::type is indeed a type. The type can be deduced at the place of call through the first argument, and the deduced type U will be substituted on the second argument. If that substitution fails the templated constructor will be discarded.

The solution above is C++03 compatible. If you have a C++11 compiler you can do the same without the need of the extra argument to the constructor (i.e. without adding an extra argument; not 100% if I am getting the syntax right :)):

template <typename T>
template <typename U, typename _ = typename enable_if_A<U>::type>
C<T>::C<U>(U const &) {...}
Comments