Substitution Failure Is Not An Error

"Substitution Failure Is Not An Error" (SFINAE) is a paradigm used the C++ language when determining which specialization of a template to use. It also refers to a template meta-programming technique which takes advantage of this paradigm.

The central part is the enable_if template, which can be implemented as follows:

   1 template<bool cond, typename T = void> struct enable_if { };
   2 template<typename T> struct enable_if<true, T> { typedef T type; };

This could for instance be used to chose whether to take argument by value or by reference based on the size of the argument's type:

   1 template<typename T, typename = void>
   2 struct argument_traits
   3 { typedef const T type; };
   4 
   5 template<typename T>
   6 struct argument_traits<T, typename enable_if<(sizeof(T) > sizeof(double))>::type >
   7 { typedef const T& type; };
   8 
   9 template<typename I, typename P>
  10 void for_each(I begin, typename argument_traits<I>::type end, P& policy);

When argument_traits<I> is instanciated, the compiler first looks for any definitions for default argument, and substitutes the instantiation with argument_traits<I, void>. Then it looks at the specializations of argument_traits and finds the one in lines 5-7. It tries to see it the template parameter list <I, void> matches that specialization: first parameter in the specializations template paramter list is T, so T = I. Second parameter in the specializations template parameter list is typename enable_if<(sizeof(T) > sizeof(double))>::type which by substituting T with I becomes typename enable_if<(sizeof(I) > sizeof(double))>::type. Now what happends depends on the size of I.

Lets first assume that the I is larger than a double. Then the condition is true and enable_if<true>::type is void, so the specialization of argument_traits matches the given template paramters and is used. The type of the second argument of for_each() the resolves to const I&.

The other case is that I is of smaller or equal size as a double. The the condition evaluates to false and the unspecialized version of enable_if gets used. Next the compiler tries to access the member type of the unspecialized enable_if -- and fails, since no such member exists. This is the substitution failure. Since the compiler follows the paradigm "substitution failure is not an error" it simply disregards the specialization of argument_traits. Instead it looks for other candidates and finds the unspcialized argument_traits template. So the type of the second argument of for_each() resolves to const I.

Of course the decision of wether to use pass by value or pass by reference is probably better left to the compiler's optimization routines.

You don't actually have to implement enable_if yourself -- if your compiler support the upcoming C++ standard you probably already have std::enable_if. Some compilers have std::tr1::enable_if. And of course you can always use boost::enable_if

Jö: SubstitutionFailureIsNotAnError (last edited 2010-07-11 17:32:06 by )