SFINAE Principle

From Wiki

Jump to: navigation, search

Contents

[edit] A Simple Example

The SFINAE principle (Substitution Failure Is Not An Error) in C++ is a useful compile-time technique that can be used to make decisions based on types at compile-time and exclude function templates from being considered for overload resolution based on that decision. Consider the following example:

void f(float);
 
template <typename T>
void f(T);
 
int main()
{
    f(0.0f);    // ok; calls f(float)
    f(0.0);     // ok; calls f<T> with T = double
    f("abc");   // ok; calls f<T> with T = const char*
    return 0;
}

Which function f is going to be called for the call f("abc")? Roughly, the compiler first selects all functions that can potentially match the given argument. Then it discards functions which require a conversion of less priority. If there's no unique match the compiler emits an ambiguous overload error. In the example above it will choose the function template because the argument "abc" is of type const char* and therefore no conversion is required for the function template.

[edit] SFINAE to the Rescue

What if, however, we don't want the function template to be a fall back for every type of argument passed to f that is not a float or implicitly convertible to a float? Could we limit calls to f only for certain types, say, double, but cause a compile error for any other type? The answer is yes, with the help of the SFINAE principle. SFINAE kicks in when the compiler tries to find all function overloads that can match a given argument type. This phase of overload resolution is called substitution. The compiler will test all available function declarations with the name f to see if they are well-formed given the argument type. If a function template is not well-formed, however, the compiler just moves on to the next function or function template and, here's the important point, it does not emit a compilation error.

To summarize, the essence of the SFINAE principle is this: If an invalid argument or return type is formed when a function template is instantiated during overload resolution, the function template instantiation is removed from the overload resolution set and does not result in a compilation error.

[edit] An Improved, but still Simple Example

Let's revise the example above and change it so that the template function f only accepts arguments of type T = double.

void f(float);
 
// define a helper struct to trigger SFINAE; note in particular that it doesn't have a nested type called 'type'
template <typename T>
struct S {};
 
template <>
struct S<double>
{
    typedef void type;
};
 
template <typename T>
void f(T, typename S<T>::type* = 0);
 
int main()
{
    f(0.0f);   // ok; calls f(float)
    f(0.0);    // ok; calls f<T> with T = double
    f("abc");  // compile error; not convertible to float and f<T> is disabled because of SFINAE
    return 0;
}

In this version of the example, f takes a second argument, a pointer to type S<T>::type, which has a default argument of 0. Note that S does not have a nested type since it is an empty struct. However, there's a specialization of S for double, which has a nested type. So when the compiler performs substitution for overload resolution for the call f(0.0) the function declaration void f(double, typename S<double>::type* = 0) is well-formed and will be considered for overload resolution. Since it is an exact match for the given argument 0.0 of type double it is the function called. The call f("abc"), however, triggers a compile-time error because the function declaration void f(const char*, typename S<const char*>::type* = 0) is not well-formed. Therefore, it is removed from the set of functions considered for overload resolution without generating an error. In this example, the error is generated later on. Since void f(float) is not a unique match for f("abc") the compiler emits an error because it cannot find a valid function overload.

Note that above I wrote "If an invalid argument or return type is formed...". In the given example I used an invalid argument type in the form of S<T>::type* and got rid of it in the function's interface by setting it to a default value of 0. Sometimes it might be preferable to use an invalid return type instead. The function template f could have been declared with this signature instead:

template <typename T>
typename S<T>::type f(T);

This is usually done if f is a function that returns a value of type T. Then S<T>::type would simply be equal to T for the types the function template should be used and is ill-formed otherwise. However, as in the example, you could also let S<T>::type simply be void for types T for which the function template should be enabled. Then the interface of f doesn't change.

[edit] Boost's enable_if

Now that we've seen the SFINAE principle in action and know about its usefulness to make compile-time decisions to explicitly enable or disable function template instantiations, I want to stress that for clarity you shouldn't be writing all this boiler-plate code by yourself. Instead, you should use the utility library enable_if [1], which is available in the Boost libraries. The enable_if family of templates use the SFINAE principle to enable or disable function templates or class template specializations based on compile-time decisions based on the template arguments. We haven't talked about using SFINAE for class template specializations so far, but you can use SFINAE in a similar way described above for them as well.

[edit] A More Advanced Example

I wrote another article on automatically making a binary function template symmetric without requiring two specializations of the underlying function object template class for a pair of different types T1 and T2, one for the order T1 after T2 and one for the order T2 after T1. This technique uses SFINAE to determine at compile-time which specialization is available and then automatically swap the order of the types if necessary before calling the specialization. See the article Implementing Symmetric Functions Without Repeating Yourself.

[edit] References

  1. Jaakko Järvi, Jeremiah Willcock & Andrew Lumsdaine, enable_if, 2003
Personal tools