More C++ Idioms/SFINAE

From Wikibooks, open books for an open world
Jump to: navigation, search

SFINAE[edit]

Intent[edit]

Prune functions that do not yield valid template instantiations from a set of overloaded functions.

Also Known As[edit]

Substitution Failure Is Not An Error

Motivation and Solution[edit]

Strictly, SFINAE is a language feature and not an idiom. However, this language feature is exploited in a very idiomatic fashion using enable-if.

In the process of template argument deduction, a C++ compiler attempts to instantiate signatures of a number of candidate overloaded functions to make sure that exactly one overloaded function is available as a perfect match for a given function call. If an invalid argument or return type is formed during the instantiation of a function template, the instantiation is removed from the overload resolution set instead of causing a compilation error. As long as there is one and only one function to which the call can be dispatched, the compiler issues no errors.

For example, consider, a simple function multiply and its templatized counter-part.

long multiply(int i, int j) { return i * j; }
 
template <class T>
typename T::multiplication_result multiply(T t1, T t2)
{
  return t1 * t2;
}
int main(void)
{
  multiply(4,5);
}

Calling function multiply in main causes the compiler to instantiate the signature of the templatized function even though the first multiply function is a better match. During instantiation an invalid type is produced: int::multiplication_result. Due to SFINAE, however, the invalid instantiation is neglected automatically. At the end, there is exactly one multiply function that can be called. So the compilation is successful.

SFINAE is often exploited in determining properties of types at compile-time. For instance, consider the following is_pointer meta-function that determines at compile-time if the given type is a pointer of some sort.

template <class T>
struct is_pointer
{
  template <class U>
  static char is_ptr(U *);
 
  template <class X, class Y>
  static char is_ptr(Y X::*);
 
  template <class U>
  static char is_ptr(U (*)());
 
  static double is_ptr(...);
 
  static T t;
  enum { value = sizeof(is_ptr(t)) == sizeof(char) };
};
 
struct Foo {
  int bar;
};
 
int main(void)
{
  typedef int * IntPtr;
  typedef int Foo::* FooMemberPtr;
  typedef int (*FuncPtr)();
 
  printf("%d\n",is_pointer<IntPtr>::value);        // prints 1
  printf("%d\n",is_pointer<FooMemberPtr>::value);  // prints 1
  printf("%d\n",is_pointer<FuncPtr>::value);       // prints 1
}

The is_pointer meta-function above would not work without SFINAE. It defines 4 overloaded is_ptr functions, three of which are templates that accept one argument each: a pointer to a variable, pointer to a member variable, or a simple function pointer. All three functions return a char, which is deliberate. The last is_ptr function is a catch-all function that uses ellipsis as it parameter. This function, however, returns a double, which is always greater in size compared to a character.

When the is_pointer is passed a type that is really a pointer (e.g., IntPtr), value is initialized to true as a result of the comparison of two sizeof expressions. The first sizeof expression calls is_ptr. If at all it is a pointer, only one of the overloaded template functions match and not others. Due to SFINAE, however, no error is raised because at least one function is found to be suitable. If none of the functions are suitable, the function with ellipsis is used instead. That function however, returns a double, which is larger than a character and so the value is initialized to false as the comparison of sizeof fails.

Note that, none of the is_ptr functions have definitions. Only declarations are sufficient to trigger SFINAE rule in the compiler. Those functions themselves must be templates, however. That is, a class template with regular functions will not participate in SFINAE. The functions that participate in SFINAE must be templates.

Known Uses[edit]

Related Idioms[edit]

References[edit]