5

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;}
};
Gilles
  • 129
  • 7
  • 2
    SFINAE is based upon function template overloading. That is, you need to have overloaded function templates (the ones that can fail have to be templates), not just overloaded functions. If a non-template function contains an invalid type, your program is ill-formed. If that function is a function template, the special SFINAE rule applies and the next viable function in the overload mechanism is chosen. – dyp Apr 30 '13 at 13:18
  • 1
    @DyP: Not necessarily *function* template, it can also be applied to *class* templates, but it must be applied on the *template* directly, not on any members of the template. – David Rodríguez - dribeas Apr 30 '13 at 13:23
  • @DavidRodríguez-dribeas That would be selecting a (partial) specialization? I don't think I've ever seen SFINAE applied to class templates instead of function templates.. – dyp Apr 30 '13 at 13:31
  • 1
    @DyP: Take a look at the section *Enabling class specializations* in the [boost enable_if](http://www.boost.org/doc/libs/1_53_0/libs/utility/enable_if.html) documentation. It has a couple of examples where enable if is used to discard some specializations in favor of others, all of it applied to a type, not a function. While they are *specializations* it **is** SFINAE, not plain good ol' selection based on partial orderings – David Rodríguez - dribeas Apr 30 '13 at 13:36
  • @DavidRodríguez-dribeas +1 nice. Though it _is_ a selection based on partial orderings (+exact matches), and even closely related to function overload resolution (same partial ordering) [temp.class.order] – dyp Apr 30 '13 at 13:44
  • @DyP: Yes, it is both partial SFINAE *and* partial ordering. There can be different specializations for which the partial ordering does not determine neither of them to be *more specialized* than the other that will be selected by SFINAE (actually SFINAE kicks in before the partial ordering... if the substition fails it is removed from the set of specializations) – David Rodríguez - dribeas Apr 30 '13 at 14:05
  • You may be aware of this, but SFINAE is not even necessary in your example, you can simply make two constructors, one of which takes `A` and the other of which takes `B`. I think because SFINAE isn't necessary for your example, it makes it harder to see how to use SFINAE. – Vaughn Cato Apr 30 '13 at 14:52

2 Answers2

9
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 at a template level, but you are trying to apply it to a member of the 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 &) {...}
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • I guess I have to write this fonction definition outside of the class template declaration. But I still can not do it (I put one of my attempts at the end of my post. I'm sorry but I tried for so long that I don't understand anything anymore. I had error: ISO C++ forbids declaration of ‘C’ with no type [-fpermissive] // error: function template partial specialization ‘C’ is not allowed // error: no ‘int C::C(const A&, typename enable_if_A::type*)’ member function declared in class ‘C – Gilles Apr 30 '13 at 14:27
  • 1
    @Gilles: Inside or outside of the template class definition does not make a difference, the important bit is that if you want SFINAE then the constructor itself must be a template. I used the out-of-class syntax as to avoid having to rewrite the wrapping class in each snippet – David Rodríguez - dribeas Apr 30 '13 at 14:33
  • I'm still trying and I still can not... Do you have an idea of what I can do wrong to have the message : error : function template partial specialization ‘C’ is not allowed ? – Gilles Apr 30 '13 at 14:59
  • OK, I found a way to do so (see above), but it's a little different from what you propose, that I can't make work and I still don't understand why. Thank you very much anyway, for taking the time to help me. – Gilles Apr 30 '13 at 16:30
  • @Gilles: I am not sure what you tried, but the code you posted at the end is what I meant to suggest, with the only difference that I provided the definition out of class (the declarations would still to be in the class) I could go into details trying to explain each one of the cases that failed and why they failed, but that is probably more time than I have to get it right and at the same time most of them fall within what is already in this answer: SFINAE cannot be applied to a non-template... – David Rodríguez - dribeas Apr 30 '13 at 20:16
  • OK, but in my other attemps, I had template constructors but the problems still occured. I think the real problem that I didn't understand is that errors are treated as SFINAE only if it occurs because of a direct template parameter. But this restriction is not a problem because you can use enable_if::value>::type to bind a function template parameter to a class template parameter. Is that correct ? – Gilles Apr 30 '13 at 22:36
  • 1
    @Gilles: I need to revisit how I write answers I guess. Yes, SFINAE only applies at the template level *directly*, not on members or otherwise. Inside the class template, the type `T` used to instantiate the class is fixed, it is thus there is no Substitution to be Failed, thus no SFinae. Regarding the *binding* the function parameter to the member template parameter, it is not different than any other type, so yes you can do `is_same` in the same way as you can do `is_same` or apply any other metafunction related or not to the enclosing template's arguments. – David Rodríguez - dribeas May 01 '13 at 02:12
0

If you're not insisting on using SFINAE, you can easily solve this problem by using an indirect constructor with partial specialization like this:

#include <iostream>

using namespace std;

template <typename T>
struct C
{
 T val;
 C() { init(); };
 void init() {};
};

template<> void C<string>::init() { val = "STR"; }
template<> void C<int>::init() { val = 5; }

int main () {
  C<int> x;
  C<string> y;

  cout << y.val << endl << x.val << endl;
}

This example is using int and string rather than your types A< U > and B< U >, but that should make no difference (except that it makes this example compilable).

kmh
  • 391
  • 3
  • 13