views:

1912

answers:

3

I want to get into more template meta-programming. I know that SFINAE stands for "substitution failure is not an error." But can someone show me a good use for SFINAE?

+10  A: 

Heres one example (from here):

template<typename T>
class IsClassT {
  private:
    typedef char One;
    typedef struct { char a[2]; } Two;
    template<typename C> static One test(int C::*);
    // Will be chosen if T is anything except a class.
    template<typename C> static Two test(...);
  public:
    enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
    enum { No = !Yes };
};

When IsClassT<int>::Yes is evaluated, 0 cannot be converted to int int::* because int is not a class, so it can't have a member pointer. If SFINAE didn't exist, then you would get a compiler error, something like '0 cannot be converted to member pointer for non-class type int'. Instead, it just uses the ... form which returns Two, and thus evalutates to false, int is not a class type.

Greg Rogers
What does ... mean in C++?
rlbond
@rlbond, i answered your question in the comments to this question here: http://stackoverflow.com/questions/822059/sfinae-with-invalid-function-type-or-array-type-parameters . In short: If both test functions are candidates and viable, then "..." has the worst conversion cost, and hence will never be taken, in favor of the other function. "..." is the ellipsis, var-arg thing: int printf(char const*, ...);
Johannes Schaub - litb
The link changed to http://blog.olivierlanglois.net/index.php/2007/09/01/what_is_the_c_sfinae_principle
tstenner
+2  A: 

Boost's enable_if library offers a nice clean interface for using SFINAE. One of my favorite usage examples is in the Boost.Iterator library. SFINAE is used to enable iterator type conversions.

David Joyner
+9  A: 

I like using SFINAE to check boolean conditions.

template<int I> void div(char(*)[I % 2 == 0] = 0) {
    /* this is taken when I is even */
}

template<int I> void div(char(*)[I % 2 == 1] = 0) {
    /* this is taken when I is odd */
}

It can be quite useful. For example, i used it to check whether an initializer list collected using operator comma is no longer than a fixed size

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, char(*)[M <= N] = 0) { /* ... */ }
}

The list is only accepted when M is smaller than N, which means that the initializer list has not too many elements.

The syntax char(*)[C] means: Pointer to an array with element type char and size C. If C is false (0 here), then we get the invalid type char(*)[0], pointer to a zero sized array: SFINAE makes it so that the template will be ignored then.

Expressed with boost::enable_if, that looks like this

template<int N>
struct Vector {
    template<int M> 
    Vector(MyInitList<M> const& i, 
           typename enable_if_c<(M <= N)>::type* = 0) { /* ... */ }
}

In practice, i often find the ability to check conditions a useful ability.

Johannes Schaub - litb
+1 quite explanatory.
Tom