Jonathan Boccara's blog

How to Make SFINAE Pretty – Part 2: the Hidden Beauty of SFINAE

Published May 18, 2018 - 10 Comments

Before we start again, 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 again, we can start 🙂

As we’ve seen in How to Make SFINAE Pretty – Part 1: What SFINAE Brings to Code, SFINAE in code is as pretty as a windmill in a field. That is, not very pretty.

But like a windmill, it’s useful. SFINAE helps deactivate a piece of template code depending on a condition, and that can be very convenient.

For instance, our motivating example was to remove the second overload of this class template, in the case where T is a reference (because in that case, it prevent the class from compiling):

template<typename T>
class MyClass
{
public:
    void f(T const& x){}
    void f(T&& x){}
};

And we ended up with an expression that works, but whose appearance is a slight to those who love to look at beautiful code:

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){}
};

If you’re not sure about how this works exactly, have a look at Part 1.

Now let’s put some makeup over that poor expression, to make it look presentable in our code.

To do this, we’ll use amongst others some techniques that Stephen Dewhurst has presented in his talk Modern C++ Interfaces.

SFINAE pretty C++ enable_if template

This post is part on the series on SFINAE:

*_t, *_v and {}

One of the burdens of the SFINAE expression is all the little things such as ::type, typename and ::value that don’t add any meaning to the expression, but are there for technical reasons. Let’s see how to get rid of them.

*_t

C++14 adds a variation of std::enable_if: std::enable_if_t. It is just an alias for accessing the ::type inside std::enable_if. Its implementation is this:

template< bool Condition, typename T = void >
using enable_if_t = typename std::enable_if<Condition, T>::type;

Since it is based on template aliases, this implementation is also compliant with C++11. So if you’re not in C++14 but in C++11 only,  you can just an implementation like the one above.

std::enable_if_t allows for a shorter syntax:

enable_if_t<a_certain_condition, MyType>

as opposed to:

typename enable_if<a_certain_condition, MyType>::type

In fact, the other template classes that have a ::type in the standard library also get a _t counterpart in C++14. This includes std::decay_t and std::conditional_t for example.

*_v

In a similar way, the templates that contain a ::value, such as std::is_reference or std::is_const, get a *_v counterpart in C++17.

Their implementation looks like this:

template<typename T>
inline constexpr bool is_reference_v = is_reference<T>::value;

This uses both a feature of C++14 (variable templates) and of C++17 (inline variables).

By using these features (depending on which version of C++ you have at hand), our SFINAE expression can be reduced from this:

typename std::enable_if<!std::is_reference<T_>::value, std::nullptr_t>::type = nullptr;

down to this:

std::enable_if_t<!std::is_reference_v<T_>, std::nullptr_t> = nullptr;

{}

If you have C++11 (and not C++14 or C++17), you can still shorten the is_reference bit of the expression, by instantiating it with braces {} inside the template call:

std::enable_if_t<!std::is_reference<T_>{}, std::nullptr_t> = nullptr;

The bit we focus on here is this:

std::is_reference<T_>{}

This instantiates a value of type std::is_reference<T_>, which inherits from std::true_type (respectively to std::false_type, depending on whether T_ is a reference or not). And std::true_type (resp. std::false_type) is implicitly convertible to bool, giving out the value true (resp. false). Thanks to Vittorio Romeo that took the time to explain this to me on his website.

A place where SFINAE won’t get in the way

Here is an idea that Stephen Dewhurst has presented in one of his inspiring talks at CppCon: Modern C++ Interfaces. This talks contains lots of insights and changed my way of coding template interfaces. I recommend that you watch it.

C++11 introduced default template parameters for functions (and class methods) templates. Indeed, in C++98, only class templates could have default values for template types:

template<typename T = int>
class MyClass
{
    // ...
};

And in C++11 we can also write:

template<typename T = int>
void myFunction()
{
    // ...
}

In fact, if we don’t use this parameter in the body of the function, we can even omit its name:

template<typename = int>
void myFunction()
{
    // ...
}

What would be the point of such a template parameter that we can’t use?

Well, it can host our SFINAE expression! Indeed, since we can put any type in a template parameter, including void, we don’t have to resort to finding a dummy type like nullptr_t for resolving the SFINAE. Conveniently enough, std::enable_if has a default value for its underlying type, which is void.

So our SFINAE expression is now reduced from this:

std::enable_if_t<!std::is_reference_v<T_>, std::nullptr_t> = nullptr;

to this:

std::enable_if_t<!std::is_reference_v<T_>>

And its position in the overload would be this:

template<typename T>
class MyClass
{
public:
    void f(T const&  x){}
    
    template<typename T_ = T, typename = std::enable_if_t<!std::is_reference_v<T_>>>
    void f(T&& x){}
};

Encapsulating the technical machinery

We could arguably stop here, but there is still a layer of template machinery that we could remove from this interface. In his talk, Stephen Dewhurst advises to hide the enable_if expression behind a names that sums up its intent.

In our case here, such a name could be EnableIfIsNotReference, or perhaps just IsNotReference.

So let’s define an alias for the SFINAE expression that encapsulates it behind that name:

template<typename T>
using IsNotReference = std::enable_if_t<!std::is_reference_v<T>>;

Putting it all together, our code has now become:

template<typename T>
using IsNotReference = std::enable_if_t<!std::is_reference_v<T>>;

template<typename T>
class MyClass
{
public:
    void f(T const& x){}
    
    template<typename T_ = T, typename = IsNotReference <T_>>
    void f(T&& x){}
};

As a comparison, here is what we started with:

template<typename T>
class MyClass
{
public:
    void MyClass(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){}
};

It was worth the transformation, wasn’t it? This is pretty much exactly a commit I’ve made in the NamedType library after watching Modern C++ Interfaces.

There are other ways to make other situations of SFINAE clearer, such as C++17’s if_constexpr inside a block of template code. But for an template interface, the above techniques are quite useful.

Related articles:

Don't want to miss out ? Follow:   twitterlinkedinrss
Share this post!Facebooktwitterlinkedin

Comments are closed