How to Make SFINAE Pretty – Part 1: What SFINAE Brings to Code
Before we start, have you sent in yet your most beautiful piece of code that prints 42? Towel day is coming up, so join in the celebration!!
Now that is said, we can start 🙂
I’m going to assume that you’re a busy developer, living and working in a busy city. And let’s say that you decide to take a few days off and steal away to a more peaceful place.
Picture yourself trying to revitalize yourself by going off to the countryside, or to taking a few days near the sea. You’re staring in awe at an vast wheat field, or to the endlessness of the ocean, wondering at the beauty of nature.
But right in the middle of your communion with the world sits a gigantic pole in metal, topped off with three blades whirling away at the wind that blows around them. A windmill. Many windmills actually, a whole wind farm is ruining your reunion with the purity of a natural landscape.
BUT, they’re useful! They generates lots of energy. For all I know, the computer I’m typing on right now may have been powered at the expense of the visual pleasure of somebody’s vacation.
SFINAE is a bit like a windmill. It sits as a wart in the middle of an interface, BUT it’s useful to create elaborate static polymorphism, in particular before C++17 and if constexpr
, and even in some use cases in C++17.
I thought we had to live with this tradeoff, until I watched a talk from Stephen Dewhurst at CppCon. In this inspring talk, Stephen shows how to turn SFINAE around to make it very expressive in an interface. Watching this talk changed my way of coding template interfaces.
However, SFINAE is a pretty advanced notion of C++, and I want everyone onboard to be able to benefit from the value of Stephen’s talk.
For this reason, we’ll split this topic into two articles:
- How to make SFINAE pretty – Part 1: what value SFINAE brings to code
- How to make SFINAE pretty – Part 2: the hidden beauty of SFINAE
So for now, let’s go over what SFINAE is.
Even if you’re comfortable with SFINAE, make sure you’re clear on the example on class methods that we tackle in the last section, since it’s a little different than SFINAE on functions, and we’ll build on this example for the Part 2 of this series.
Deactivating a piece of template code
Let’s wait no more to hear what SFINAE is used for: the point of SFINAE is to deactivate a piece of template code for certain types.
As a motivating example, we’ll use a situation we encountered when passing strong types by reference. Stripping away the context of strong types in order to focus on SFINAE, it boils down to a template class that has two overloads:
template<typename T> class MyClass { public: void f(T const& x); void f(T&& x); };
One takes a const lvalue reference, and the other an rvalue reference. Actually, you don’t even need to know anything about rvalues and lvalues to understand the issue here (but if you want to know about them, check out the article on lvalues, rvalues and their references).
The only thing you need to know here is that using this class won’t compile when T
is itself a reference. Indeed, the following code:
using MyClassOnRef = MyClass<int&>; int i = 0; MyClassOnRef x; x.f(i);
leads to a compilation error:
error: 'void MyClass<T>::f(T&&) [with T = int&]' cannot be overloaded with 'void MyClass<T>::f(const T&) [with T = int&]'
What to do about it?
When you think about it, we don’t need the second overload:
void f(T&& x);
when T
is a reference.
How could we get rid of this overload only when T
is a reference?
With SFINAE!
SFINA-what?
E.
Let’s go over a piece of code to illustrate how the mechanism works. Consider this template function f
that has two overloads:
template<typename T> void f(T x, typename T::type y) { // ... } template<typename T> void f(T x, typename T::other_type y) { // ... }
Let’s create a type A
that contains a type called type
:
struct A { using type = int; };
This isn’t business code but bear with me, it’s for the purpose of illustrating SFINAE. We’ll get back to our real use case later on.
Now what if we call f
this way:
f(A(), 42);
The compiler needs to know which of the two overloads of f
we’re talking about. So it’s going to substitute T
for the parameter we’re passing, which is A
here. Actually, the compiler probably does a way more sophisticated analysis, but in essence the substitution of the first overload will look like this:
void f(A x, typename A::type y) { // ... }
which is a good fit for our call, since A::type
exists and is int
. But when it substitutes A
in the second overload, it gives this:
void f(A x, typename A::other_type y) { // ... }
But A
doesn’t have a type called other_type
! This substitution has failed. And shouldn’t a substitution failure be an error and halt compilation?
No. C++ says that a Substitution Failure Is Not An Error. In fact, this is exactly what SFINAE means: if you look closely you’ll observe that the letters of SFINAE form the acronyms of that sentence.
So, if it’s not error, what’s going to happen?
Nothing. The compiler simply disregards this particular piece of code when it comes to instantiating f
with A
, as if it wasn’t written.
So the point of SFINAE is to deactivate a piece of template code for some types.
enable_if
Is there a way to better control what code to deactivate, depending on a condition? Like, in our initial example, deactivate a overload if a certain type is a reference for example?
It turns out there is, and this can be implemented with enable_if
.
enable_if
has been standardized in C++ in C++11, has been in Boost since forever, and can be easily replicated even if you don’t use any of the former. Here is an implementation, compliant with C++98:
template<bool Condition, typename T = void> struct enable_if { }; template<typename T> struct enable_if<true, T> { typedef T type; };
Let’s analyze this code. enable_if
is a template that takes a boolean Condition
and a type T
as template parameters.
If that Condition
is true
, enable_if
has an underlying type called type
.
If that Condition
is false
, enable_if
has no underling type.
Then invoking an expression that looks like this:
typename enable_if<a_certain_condition, MyType>::type
will trigger SFINAE and therefore take away with it all its containing template declaration when a_certain_condition
is false
. This declaration is then colloquially known to be “SFINAE’d away”.
But when a_certain_condition
is true
, all this expression resolves itself into MyType
.
So the expression let’s its containing code enabled if a_certain_condition
holds. Hence its name.
SFINAE on a method of a class template
We now have almost everything we need to resolve our initial problem: discarding the second overload of this class when T
is a reference:
template<typename T> class MyClass { public: void f(T const& x); void f(T&& x); };
The idea is to include in that overload an enable_if
based on the condition of T
being a reference, to make it go away.
How do we know if T
is a reference? By using std::is_reference
. Like enable_if
, it was standardized in C++11, has been in Boost since the beginning of time, and can be replicated in C++98 easily.
So we’re going to use:
typename std::enable_if<!std::is_reference<T>::value>::type
Now the question is: how do we fit this expression into the prototype of the overload?
One way to go about this is to add a new parameter to that overload:
template<typename T> class MyClass { public: void f(T const& x); void f(T&& x, typename std::enable_if<!std::is_reference<T>::value, XXX>::type); };
But in the case where T
is not a reference and the overload is kept , std::enable_if
does resolve to some type, which I’ve written as XXX
in the above snippet. What should be XXX
?
Also, we wouldn’t want this technique to impact the call site of our overload. So we need a default value for this XXX
parameter. But what should that default value be?
This extra XXX
type has no meaning in itself in our case: it’s just there to support the enable_if
on the condition. But we can’t set it as void
since a method parameter can’t be void
. We could maybe define a specific empty type for this. Or, to avoid adding yet another type to that mix, we could use nullptr_t
:
template<typename T> class MyClass { public: void f(T const& x); void f(T&& x, typename std::enable_if<!std::is_reference<T>::value, std::nullptr_t>::type = nullptr); };
There is one last thing missing for this code to work, that is specific to SFINAE on the methods of template classes: from the perspective of the method, T
is in fact not a template type. It’s a template type of the class, and not one of the method. And to use SFINAE of the method, we need a template type of the method.
C++11 allows to achieve this, by creating a default value a template type of the method. To end up performing SFINAE on T
, we use T
as the default value:
template<typename T> class MyClass { public: void f(T const& x); template<typename T_ = T> void f(T&& x, typename std::enable_if<!std::is_reference<T_>::value, std::nullptr_t>::type = nullptr); };
And this is it. The second overload is disregarded only when T
is a reference, and MyClass
now compiles for all types T
.
Doesn’t that look beautiful?
No.
It looks disgusting.
Before I watched Stephen’s talk, I thought we had to live with such horrors in order to let our C++ code compile.
But this isn’t true. In the next post we’ll go over two of the main ideas of this talk: how to make the SFINAE expressions readable, and how C++11 allows to put them in a place where they don’t get in the way.
Stay tuned for seeing how to make SFINAE look prettier!
Don't want to miss out ? Follow:   Share this post!