How it works...

In this recipe, you will learn how to incorporate SFINAE in your own code. To start, we must first understand what SFINAE is and how the standard library uses it to implement type traits. Without knowing how type traits are implemented, it can be difficult to understand how to use them.

To start, the most important thing to understand with SFINAE is what its name says, which is that a substitution failure is not an error. What this means is that when a template type is being substituted, if a failure occurs, the compiler will not generate an error as a result. For example, we can write the following:

#include <iostream>

struct the_answer
{
using type = unsigned;
};

template<typename T>
void foo(typename T::type t)
{
std::cout << "The answer is not: " << t << ' ';
}

template<typename T>
void foo(T t)
{
std::cout << "The answer is: " << t << ' ';
}

int main(void)
{
foo<the_answer>(23);
foo<int>(42);

return 0;
}

The output for each of these are depicted here:

The answer is: 23
The answer is: 42

In this example, we have created two versions of the foo() function. This first version takes a T type that has a type alias that we use to create the function's parameter. The second version just takes the T type itself. We then use both versions of the foo() function, one with an integer and the other with a struct that defines the type alias.

The takeaway from the preceding example is that when we call the foo<int>() version of the foo() function, the compiler doesn't generate an error when it attempts to match the int type with the version of the foo() function that takes a type with the type alias. This is what SFINAE is. All it says is that when the compiler attempts to take a given type and match it to a template, if a failure occurs, the compiler will not generate an error. The only time an error would occur is if the compiler cannot find a suitable substitution. For example, what happens if we comment out the second version of foo()? Let's see:

As you can see from the preceding error output, the compiler is even saying that the error is a substitution error. The template that we provide is not a valid candidate based on the type that was provided.

The other important takeaway from this example is that the compiler was able to pick between the two different versions of our foo() function based on the type that was provided. We can use this to our advantage. Specifically, this gives us the power to do different things based on the type that is provided. All we need is a means to write our foo() function so that we can enable/disable different versions of our templates based on the types that we are provided.

This is where std::enable_if comes into play. std::enable_if takes the idea of SFINAE to the next step, allowing us to define a type if its parameter is true. Otherwise, it will generate a substitution error, purposely forcing the compiler to pick a different version of the template. std::enable_if is defined as follows:

template<bool B, class T = void>
struct enable_if {};

template<class T>
struct enable_if<true, T> { typedef T type; };

This first defines a struct that takes bool B and a T type that defaults to void. It then defines a specialization of this struct type when bool is true. Specifically, when the bool value is true, the type that is provided is returned, which, as we stated before, defaults to void. To see how this is used, let's look at an example:

#include <iostream>
#include <type_traits>

template<typename T>
constexpr auto is_int()
{
return false;
}

template<>
constexpr auto is_int<int>()
{
return true;
}

template<
typename T,
std::enable_if_t<is_int<T>(), int> = 0
>
void the_answer(T is)
{
std::cout << "The answer is: " << is << ' ';
}

int main(void)
{
the_answer(42);
return 0;
}

The output is as follows:

In this example, we create a function called is_int() that always returns false. We then create a template specialization of this function for int that returns true. Next, we create a function that takes any type, but we add std::enable_if_t (the added _t part is a shorthand that was added to C++17 for ::type) to the template definition that uses our is_int() function. If the T type that is provided is int, our is_int() function will return true.

std::enable_if does nothing by default. If it is true, however, it returns a type alias, which, in the preceding example, is the int type that we are passing as the second parameter of std::enable_if. What this is saying is that if std::enable_if is true, it will return an int type. We then set this int type to 0, which is a valid thing to do. This doesn't generate a failure; our template function becomes a valid substitution and, therefore, is used. In summary, if T is an int type, std::enable_if turns into an int type itself that we then set to 0, which compiles without an issue. If our T type is not int, std::enable_if turns into nothing. Attempting to set nothing to 0 results in a compilation error, but since this is SFINAE, the compiler error becomes nothing more than a substitution error.

Let's look at the error case. If we set 42 to 42.0, which is a double, not int, we get the following:

As you can see from the preceding error, the compiler is saying that there is no type named type in enable_if. If you look at the definition of std::enable_if, this is expected because std::enable_if doesn't do anything if it is false. It only creates a type named type if it is true.

To better understand how this works, let's look at another example:

#include <iostream>
#include <type_traits>

template<
typename T,
std::enable_if_t<std::is_integral_v<T>>* = nullptr
>
void the_answer(T is)
{
std::cout << "The answer is: " << is << ' ';
}

int main(void)
{
the_answer(42);
return 0;
}

The output is as follows:

In the preceding example, we use std::is_integral_v, which does the same thing as our is_int() function, with the difference being that it is provided by the standard library and can handle CV types. In fact, the standard library has a massive list of different versions of these functions including different types, inheritance properties, CV properties, and so on. If you need to check for a type property of any kind, chances are that the standard library has an std:is_xxx function that you can use.

The preceding example is nearly identical to our previous one with the difference being that we do not return int in our std::enable_if method. Instead, we use * = nullptr. This works because std::enable_if returns void by default. The * character turns this void into a void pointer, which we then set to nullptr.

In the next example, we show another twist on this:

#include <iostream>
#include <type_traits>

template<typename T>
std::enable_if_t<std::is_integral_v<T>>
the_answer(T is)
{
std::cout << "The answer is: " << is << ' ';
}

int main(void)
{
the_answer(42);
return 0;
}

The output is as follows:

In this example, void for our function is created by std::enable_if. If T is not an integer, void is not returned and we see this error (instead of the code compiling and allowing us to execute it in the first place):

In summary, std::enable_if will create a type named type, which is based on the type that you provide it. By default, this is void but you can pass in any type that you want. Not only can this functionality be used to enforce a type for our templates, but it can also be used to define different functions based on the type that we are provided, as shown in this example:

#include <iostream>
#include <type_traits>
#include <iomanip>

template<
typename T,
std::enable_if_t<std::is_integral_v<T>>* = nullptr
>
void the_answer(T is)
{
std::cout << "The answer is: " << is << ' ';
}

template<
typename T,
std::enable_if_t<std::is_floating_point_v<T>>* = nullptr
>
void the_answer(T is)
{
std::cout << std::setprecision(10);
std::cout << "The answer is: " << is << ' ';
}

int main(void)
{
the_answer(42);
the_answer(42U);
the_answer(42.12345678);

return 0;
}

The output for the preceding code is as follows:

Like our first example in this recipe, we have created two different versions of the same function. SFINAE allows the compiler to pick the most suitable version based on the type that was provided.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset