This time I’d like to tackle a bit more complex problem: SFINAE. I’m not using this paradigm on a daily basis, but I’ve stumbled across it several times and I thought it might be worth trying to understand this topic.
- What is SFINAE?
- Where can you use it?
- Do you need this on a daily basis?
Let’s try to answer those questions.
In the article:
Intro
First thing: if you have more time, please read An introduction to C++’s SFINAE concept: compile-time introspection of a class member by Jean Guegant. This is an awesome article that discusses SFINAE more deeply that I’ve ever found in other places. Highly recommended resource.
Still reading? Great! :) Let’s start with some basic ideas behind this concept:
Very briefly: the compiler can actually reject code that “would not compile” for a given type.
From Wiki:
Substitution failure is not an error (SFINAE) refers to a situation in C++ where an invalid substitution of template parameters is not in itself an error. David Vandevoorde first introduced the acronym SFINAE to describe related programming techniques.
We’re talking here about something related to templates, template substitution and compile time only… possibly quite a scary area!
A quick example: see it also on coliru online cpp compiler
<code>struct Bar
{
typedef double internalType;
};
template <typename T>
typename T::internalType foo(const T& t) {
cout << "foo<T>" << endl;
return 0;
}
int main()
{
foo(Bar());
foo(0); }
</code>
We have one awesome template function that returns T::internalType
and we call it with Bar
and int
param types.
The code, of course, will not compile. The first call of foo(Bar());
is a proper construction, but the second call generates the following error (GCC):
<code> no matching function for call to 'foo(int)'
...
template argument deduction/substitution failed:
</code>
When we make a simple correction and provide a suitable function for int types. As simple as:
<code>int foo(int i) { cout << "foo(int)" << endl; return 0; }
</code>
The code can be built and run.
Why is that?
Obviously, when we added an overloaded function for the int
type, the compiler could find a proper match and invoke the code. But in the compilation process the compiler also ‘looks’ at the templated function header. This function is invalid for the int
type, so why was there not even a warning reported (like we got when there was no second function provided)? In order to understand this, we need to look at the process of building the overload resolution set for a function call.
Overload Resolution
When the compiler tries to compile a function call (simplified):
- Perform a name lookup
- For function templates the template argument values are deduced from the types of the actual arguments passed in to the function.
- All occurrences of the template parameter (in the return type and parameters types) are substituted with those deduced types.
- When this process leads to invalid type (like
int::internalType
) the particular function is removed from the overload resolution set. (SFINAE)
- At the end we have a list of viable functions that can be used for the specific call. If this set is empty, then the compilation fails. If more than one function is chosen, we have an ambiguity. In general, the candidate function, whose parameters match the arguments most closely is the one that is called.
In our example: typename T::internalType foo(const T& t)
was not a good match for int
and it was rejected from overload resolution set. But at the end, int foo(int i)
was the only option in the set, so the compiler did not reported any problems.
Where can I use it?
I hope you get a basic idea what SFINAE does, but where can we use this technique? A general answer: whenever we want to select a proper function/specialization for a specific type.
Some of the examples:
- Call a function when T has a given method (like call
toString()
if T has toString
method) - Nice example here at SO of detecting count of object passed in initializer list to a constructor.
- Specialize a function for all kind of type traits that we have (is_integral, is_array, is_class, is_pointer, etc… more traits here)
- Foonathan blog: there is an example of how to count bits in a given input number type. SFINAE is part of the solution (along with tag dispatching )
- Another example from foonathan blog - how to use SFINAE and Tag dispatching to construct range of objects in a raw memory space.
enable_if
One of the main uses of SFINAE can be found in enable_if
expressions.
enable_if is a set of tools that internally use SFINAE. They allow to include or exclude overloads from possible function templates or class template specialization.
For example:
<code>template <class T>
typename enable_if<is_arithmetic<T>::value, T>::type
foo(T t)
{
cout << "foo<arithmetic T>" << endl;
return t;
}
</code>
This function ‘works’ for all the T types, that are arithmetic (int, long, float…). If you pass other type (for instance MyClass), it will fail to instantiate. In other words, template instantiation for non-arithmetic types are rejected from overload resolution sets. This construction might be used as template parameter, function parameter or as function return type.
enable_if<condition, T>::type
will generate T
, if the condition is true
, or an invalid substitution if condition is false
.
enable_if
can be used along with type traits to provide the best function version based on the trait criteria.
As I see it, most of the time it’s better to use enable_if
than your custom SFINAE versions of the code. enable_if
is probably not that super nice looking expression, but this is all we have before concepts in C++17… or C++20.
Expression SFINAE
C++11 has even more complicated option for SFINAE.
n2634: Solving the SFINAE problem for expressions
Basically, this document clears the specification and it lets you use expressions inside decltype
and sizeof
.
Example:
<code>template <class T> auto f(T t1, T t2) -> decltype(t1 + t2);
</code>
In the above case, the expression of t1+t2
needs to be checked. It will work for two int
’s (the return type of the +
operator is still int
), but not for int
and std::vector
.
Expression checking adds more complexity into the compiler. In the section about overload resolution I mentioned only about doing a simple substitution for a template parameter. But now, the compiler needs to look at expressions and perform full semantic checking.
BTW: VS2013 and VS2015 support this feature only partially (msdn blog post about updates in VS 2015 update 1), some expressions might work, some (probably more complicated) might not. Clang (since 2.9) and GCC (since 4.4) fully handle “Expression SFINAE”.
Any disadvantages?
SFINAE, enable_if
are very powerful features but also it’s hard to get it right. Simple examples might work, but in real-life scenarios you might get into all sorts of problems:
- Template errors: do you like reading template errors generated by compiler? especially when you use STL types?
- Readability
- Nested templates usually won’t work in enable_if statements
Here is a discussion at StackOverlow: Why should I avoid std::enable_if in function signatures
Alternatives to SFINAE
- tag dispatching - This is a much more readable version of selecting which version of a function is called. First, we define a core function and then we call version A or B depending on some compile time condition.
- static_if - D language has this feature (see it here), but in C++ we might use a bit more complicated syntax to get similar outcome.
- concepts (in the near future hopefully!) - All of the mentioned solutions are sort of a hack. Concepts give explicit way to express what are the requirements for a type that is accepted by a method. Still, you can try it in GCC trunk using Concepts lite implementation
One Example
To conclude my notes it would be nice to go through some working example and see how SFINAE is utilized:
Link to online compiler, coliru
The test class:
<code>template <typename T>
class HasToString
{
private:
typedef char YesType[1];
typedef char NoType[2];
template <typename C> static YesType& test( decltype(&C::ToString) ) ;
template <typename C> static NoType& test(...);
public:
enum { value = sizeof(test<T>(0)) == sizeof(YesType) };
};
</code>
The above template class will be used to test if some given type T
has ToString()
method or not. What we have here… and where is the SFINAE concept used? Can you see it?
When we want to perform the test we need to write:
<code>HasToString<T>::value
</code>
What happens if we pass int
there? It will be similar to our first example from the beginning of the article. The compiler will try to perform template substitution and it will fail on:
<code>template <typename C> static YesType& test( decltype(&C::ToString) ) ;
</code>
Obviously, there is no int::ToString
method, so the first overloaded method will be excluded from the resolution set. But then, the second method will pass (NoType& test(...)
), because it can be called on all the other types. So here we get SFINAE! one method was removed and only the second was valid for this type.
In the end the final enum value
, computed as:
<code>enum { value = sizeof(test<T>(0)) == sizeof(YesType) };
</code>
returns NoType
and since sizeof(NoType)
is different than sizeof(YesType)
the final value will be 0.
What will happen if we provide and test the following class?
<code>class ClassWithToString
{
public:
string ToString() { return "ClassWithToString object"; }
};
</code>
Now, the template substitution will generate two candidates: both test
methods are valid, but the first one is better and that will be ‘used‘. We’ll get the YesType
and finally the HasToString<ClassWithToString>::value
returns 1 as the result.
How to use such checker class?
Ideally it would be handy to write some if
statement:
<code>if (HasToString<decltype(obj)>::value)
return obj.ToString();
else
return "undefined";
</code>
Unfortunately, all the time we’re talking about compile time checks so we cannot write such if
. However, we can use enable_if
and create two functions: one that will accept classes with ToString
and one that accepts all other cases.
<code>template<typename T>
typename enable_if<HasToString<T>::value, string>::type
CallToString(T * t) {
return t->ToString();
}
string CallToString(...)
{
return "undefined...";
}
</code>
Again, there is SFINAE in the code above. enable_if
will fail to instantiate when you pass a type that generates HasToString<T>::value = false
.
Example Improvements
The above example was using some standard, one may even say - old - techniques. We can update this a bit, and even improve (with exact return type checking).
If I am correct and assuming you have void_t
in your compiler/library, this is a new version of the code:
<code>
template< class , class = void >
struct has_toString : false_type { };
template< class T >
struct has_toString< T , void_t<decltype(&T::toString) > > : std::is_same<std::string, decltype(declval<T>().toString())>
{ };
</code>
http://melpon.org/wandbox/permlink/ZzSz25GJVaY4cvzw
Pretty nice… right? :)
It uses explicit detection idiom based on void_t
. Basically, when there is no T::toString()
in the class, SFINAE happens and we end up with the general, default template (and thus with false_type
). But when there is such method in the class, the specialized version of the template is chosen. This could be the end if we don’t care about the return type of the method. But in this version we check this by inheriting from std::is_same
. The code checks if the return type of the method is std::string
. Then we can end up with true_type
or false_type
.
For more information about void_t please watch amazing video by Walter E. Brown:
CppCon 2014: Walter E. Brown "Modern Template Metaprogramming: A Compendium, Part II"
Starting at around 29 minute, and especially around 39 minute.
Things to remember:
SFINAE works in compile time, when template substitution happens, and allows to control overload resolution set for a function.
Summary
In this post, I showed a bit of theory behind SFINAE. With this technique (plus with enable_if
), you can create specialized functions that will work on subset of types. SFINAE can control overloaded resolution set. Probably most of us do not need to use SFINAE on a daily basis. Still, it’s useful to know general rules behind it.
Some questions
Where do you use SFINAE and enable_if
?
If you have nice example of it, please let me know and share your experience!
References
Books:
History