More C++ Idioms/SFINAE
SFINAE
edit
Intent
editPrune functions that do not yield valid template instantiations from a set of overloaded functions.
Also Known As
editSubstitution Failure Is Not An Error
Motivation and Solution
editStrictly, 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 counterpart.
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 try 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, this invalid instantiation is neglected automatically. At the end, there is exactly one multiply
function that can be called, the first one. Thus 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.