In this lesson, we will learn about the SFINAE paradigm, which is an abbreviation of Substitution Failure Is Not An Error. SFINAE allows templates to remove themselves from consideration during overload resolution if their arguments do not meet certain requirements.
The lesson covers causing substitution failures, using std::enable_if
, and provides a practical example of applying SFINAE with type traits.
In our previous lesson on overload resolution, we saw that the compiler can select a function that will cause a compilation error, even if a more appropriate function was available.
In the following example, we are calling Render
with a double
. There are two candidates - a template function that accepts any type, and a Render
function that accepts a float.
Given that the template can generate a function that will be an exact match for the double
argument, the compiler elects to use the template, resulting in a compilation error:
#include <iostream>
template <typename T>
void Render(T Object) {
std::cout << "Template Function Called\n";
Object.Render();
}
void Render(float x) {
std::cout << "Non-Template Function Called\n";
}
int main() {
Render(3.14);
}
error: left of '.Render' must have class/struct/union
Naturally, we could address that at our invocation of Render()
by explicitly casting our float
to a double
but we’d rather fix this in our template.
Rather than forcing other developers to perform unnecessary explicit casts, a better design would be to prevent our template from inserting itself into situations where it can’t possibly work.
There are two main ways a template can remove itself from consideration if the arguments do not meet it's requirements:
In our previous lesson, we said that if a template causes a compilation error within its function body, that does not prevent it from being selected within the overload resolution process.
Indeed, that is what is happening in the previous example. The compiler is selecting our Render
template, even though it will cause a compilation error, and even though another candidate is available that would have worked.
There is an exception to this - when we instantiate a template, invalid code that appears within the template parameter list, function return type, or function parameter list, are handled differently.
If an issue appears in the locations we’ve marked with ***
in the following example, that is referred to as a substitution failure:
template <***>
*** Render(***) {
// Function Body
}
The SFINAE paradigm is an abbreviation for substitution failure is not an error. It means that if a substitution failure occurs when instantiating a template with an expression like Render(SomeArgument)
the compiler will not throw an error.
Instead, it will simply remove the template from the list of viable candidates to serve that expression. From there, overload resolution proceeds as normal, attempting to find the next best candidate.
SFINAE gives us a fairly inelegant way to solve the problem we introduced at the beginning of the lesson. We can set up our template such that, if an attempt is made to instantiate it with an unsupported type, we intentionally cause a substitution failure.
Variations of this idea often involve introducing an additional template or function parameter that will be invalid if a template argument doesn’t meet our requirements.
In the following example, we add a function parameter called y
, whose type will be a pointer to the type returned by the first parameter’s Render
 method.
This type is unused within our body, and we don’t expect consumers to provide an argument for it, so we give it a default value of nullptr
:
template <typename T>
void Render(
T Object,
decltype(Object.Render())* y = nullptr
) {
std::cout << "Template Function Called\n";
Object.Render();
}
Now, when Object
does not have a Render()
method, we’ll create a substitution failure, removing our template from consideration.
Below, our double
argument does not have a Render
method so, after the substitution failure, we fall back to using the Render(float)
 overload.
Our Rock{}
argument does have a Render
method, so our template is instantiated with y
having a type of void*
. A void pointer is a valid type in C++ (we’ll cover what it’s used for later in the course) so our template is selected as the preferred candidate:
#include <iostream>
struct Rock {
void Render() {
std::cout << "Rendering a Rock";
}
};
template <typename T>
void Render(
T Object,
decltype(Object.Render())* y = nullptr
) {
std::cout << "Template Function Called\n";
Object.Render();
}
void Render(float x) {
std::cout << "Non-Template Function Called\n";
}
int main() {
Render(3.14);
Render(Rock{});
}
Non-Template Function Called
Template Function Called
Rendering a Rock
If the above code feels overly complicated, don’t worry - it is. It’s widely acknowledged that SFINAE is a complex and inelegant solution to this problem.
So much so that C++20 introduced a new language feature called concepts that removes the need for SFINAE-based techniques.
In the next lesson, we’ll demonstrate how we could create a concept called Renderable
, which expresses a set of requirements of a type. Then, we can specify a template type has those requirements simply by replacing typename
in our argument list with our concept’s name:
template <Renderable T>
void Render(T Object) {
Object.Render();
}
However, concepts are not yet widely implemented in the projects we’ll be working on, so knowledge of SFINAE is likely to be useful for some time to come.
std::enable_if
The std::enable_if
template within the <type_traits>
library is designed to make the conditional creation of substitution failures slightly more elegant. It accepts two template arguments - a boolean expression that can be evaluated at compile time, and a type. It then returns a struct.
If our boolean was true, the struct would have a type
static member matching the type we provided as the first argument. If the boolean was false, there will be no such member, resulting in invalid code if we try to use it.
#include <type_traits>
int main() {
// this will be an int
std::enable_if<true, int>::type IntA{1};
// this will be a compilation error
std::enable_if<false, int>::type IntB{2};
}
error: 'type': is not a member of 'std::enable_if<false,int>'
As with most standard library traits, we can alternatively use the std::enable_if_t
variation orm to access the type
 directly:
#include <type_traits>
int main() {
// this will be an int
std::enable_if<true, int>::type IntA{1};
// this will be an int too
std::enable_if_t<true, int> IntB{2};
}
Let’s use SFINAE in a more realistic project structure. Below, we’ve reintroduced the is_renderable
type trait we created in the previous lesson. Our Rock
type satisfies this trait, whilst no other type does.
Without SFINAE, our attempt to call Render()
with a double
fails as before:
// Rendering.h
#pragma once
#include <type_traits>
#include <iostream>
template <typename>
struct is_renderable : std::false_type {};
template <typename T>
constexpr bool is_renderable_v{
is_renderable<T>::value};
template <typename T>
void Render(T Object) {
std::cout << "Template Function Called\n";
Object.Render();
}
// Rock.h
#include <iostream>
#include "Rendering.h"
#pragma once
struct Rock {
void Render() {
std::cout << "Rendering a Rock";
}
};
template <>
struct is_renderable<Rock>
: std::true_type {};
// main.cpp
#include <iostream>
#include "Rendering.h"
#include "Rock.h"
void Render(float x) {
std::cout << "Non-Template Function Called\n";
}
int main() {
Render(3.14);
Render(Rock{});
}
error: left of '.Render' must have class/struct/union
Within Rendering.h
, we can now apply our knowledge of SFINAE with the help of std::enable_if
and our is_renderable
trait. We’ll introduce an additional, unused template parameter to the Render()
 template.
If our first template parameter (the T
typename) satisfies the is_renderable
concept, our second parameter will have a type of int
. If T
does not satisfy is_renderable
, our second parameter will have no type at all.
In other words, if T
does not satisfy the is_renderable
trait, our template will generate a substitution failure, thereby removing it from the candidate list:
// Rendering.h
#pragma once
#include <type_traits>
#include <iostream>
template <typename>
struct is_renderable : std::false_type {};
template <typename T>
constexpr bool is_renderable_v{
is_renderable<T>::value};
template <typename T, std::enable_if_t<
is_renderable<T>::value, int> = 0>
void Render(T Object) {
std::cout << "Template Function Called\n";
Object.Render();
}
Now, our template is no longer interfering with the code in main.cpp
, and our program works as intended:
// main.cpp (Unchanged)
#include <iostream>
#include "Rendering.h"
#include "Rock.h"
void Render(float x) {
std::cout << "Non-Template Function Called\n";
}
int main() {
Render(3.14);
Render(Rock{});
}
Non-Template Function Called
Template Function Called
Rendering a Rock
In this lesson, we learned about the SFINAE paradigm, and how we can use it. The key points to remember are:
std::enable_if
Learn how SFINAE allows templates to remove themselves from overload resolution based on their arguments, and apply it in practical examples.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.