SFINAE—Substitution Failure Is Not An Error—is a cornerstone of modern C++ template metaprogramming. It allows the compiler to silently discard ill‑formed template specializations during overload resolution, enabling you to write highly generic, type‑safe code without cluttering your API with explicit enable_if constraints everywhere.
1. What is SFINAE?
When the compiler substitutes template arguments into a function or class template, it checks whether the resulting type or expression is valid. If it isn’t, instead of treating that as a hard error, the compiler simply removes that candidate from the overload set. The “failure” is not an error—hence the name.
2. Basic Syntax
The classic idiom uses std::enable_if:
template<typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
void foo(T x) {
std::cout << "Integral overload: " << x << '\n';
}
If T is not integral, the substitution of enable_if_t fails, and this overload disappears from consideration.
You can also use a trailing return type:
template<typename T>
auto bar(T&& x) -> std::enable_if_t<std::is_floating_point_v<T>, void> {
std::cout << "Floating point overload: " << x << '\n';
}
Both patterns achieve the same goal but differ stylistically.
3. More Powerful SFINAE: Using decltype
Sometimes you want to enable an overload based on whether a type supports a particular expression:
template<typename T>
auto has_serialize(int) -> decltype(std::declval <T>().serialize(), std::true_type{});
template<typename T>
std::false_type has_serialize(...);
template<typename T>
void serialize_if_possible(T& obj) {
if constexpr (decltype(has_serialize <T>(0))::value) {
obj.serialize();
} else {
std::cout << "Serialization not supported.\n";
}
}
Here the first overload of has_serialize is selected only if obj.serialize() is a valid expression; otherwise the ellipsis overload is chosen, and `has_serialize