Concepts versus SFINAE-based constraints

In some situations, we need to makes sure function templates can only be invoked with some specific types. SFINAE (that stands for Substitution Failure Is Not An Error) is a set of rules that specify how compilers can discard specializations from the overload resolution without causing errors. A way to achieve this is with the help of std::enable_if.

Let us look at an example. Suppose we want to write a function template called product() that returns the product of its two arguments. We only want to be able to call it with arithmetic types. Using std::enable_if we can define such a function as follows:

We can use it to multiply integers or doubles for example, even booleans (bools can be converted to integers, with true becoming 1 and false becoming 0), but not other types, such as std::string.

The last line above would produce the following compiler error when compiling with Visual Studio:

SFINAE-based constraints are not the most intuitive code to read. Eventually, they model concepts with template trickery. But C++20 provides concepts as a first-class experience to make metaprogramming simpler and more expressive both to the compiler and developers. So let us look at how we can do the same and better with concepts.

We can start by providing concepts for numerical types (a type that is either integral, or floating-point). You can see these below. Notice that the standard library provides two concepts called std::integral and std::floating_point in the header <concepts>. The implementation below is identical to the standard one.

Having this numeric concept available, we can change the definition of the product() function to the following:

Compiling the lines above would again produce an error for the last invocation of product() using std::string arguments. This time, the errors yield by the Visual Studio compiler are as follows:

But what if we want to extend the function template product() so that it works for every type for which the operator* is overloaded? That’s difficult to do with SFINAE but rather straight-forward with concepts. The only thing we need to do is define a concept that expresses that. Below, this concept is called multiplicative.

The changes to the definition of product() are minimal: we just replace numeric with multiplicative.

So what can we do to make product("one"s, "two"s) compile? We can overload operator* for std::string. The following is an implementation that “zips” two strings together. The product of “abc” and “xywz” is “axbycwz”. The actual implementation is not important; this is provided just for the sake of making the example produce some actual values.

With this available the code we’ve seen above compiles without errors.

And that’s how simple concepts can make the code. More about the advantages of concepts can be found here: Why I want Concepts, and why I want them sooner rather than later.

See also on this topic:

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.