We want this behavior:
divide(10, 2); // OK
divide(10.0, 2.0); // OK
divide(std::string{}, std::string{}); // ❌ not allowedBut C++ templates are instantiated only when used, so we need a way to:
- Enable a function for some types
- Disable it for others
- Without producing a compilation error
This is exactly what SFINAE does.
SFINAE means:
Substitution Failure Is Not An Error
When the compiler tries to substitute a template type, and that substitution fails, the compiler:
- ❌ does not error
- ✅ simply removes that function from overload resolution
So the function silently disappears.
We first ask:
“Can I write
a / bfor this type?”
template <typename T, typename = void>
struct has_division : std::false_type {};This says:
- By default,
Tdoes not support division
template <typename T>
struct has_division<
T,
std::void_t<decltype(std::declval<T>() / std::declval<T>())>
> : std::true_type {};What happens here:
std::declval<T>()creates a fake T (no object needed)decltype(a / b)checks if the expression is validstd::void_t<...>turns a valid expression intovoid
If T / T is:
- ✅ valid → this specialization exists →
true_type - ❌ invalid → substitution fails → specialization ignored
This is SFINAE in action.
template <typename T, std::enable_if_t<has_division<T>::value, int> = 0>
T divide(const T& a, const T& b) {
return a / b;
}Key idea:
enable_if_t<condition, int>exists only if condition is true- If
has_division<T>::value == false→ the function is removed from overload resolution
template <typename T, std::enable_if_t<!has_division<T>::value, int> = 0>
void divide(const T&, const T&) {
std::cout << "No division available for this type\n";
}So now:
| Type | Which overload exists |
|---|---|
int |
division overload |
double |
division overload |
std::string |
fallback overload |
#include <iostream>
#include <string>
#include <type_traits>
#include <utility>
// 1) Detect division support
template <typename T, typename = void>
struct has_division : std::false_type {};
template <typename T>
struct has_division<
T,
std::void_t<decltype(std::declval<T>() / std::declval<T>())>
> : std::true_type {};
// 2) Enabled only if T supports division
template <typename T, std::enable_if_t<has_division<T>::value, int> = 0>
T divide(const T& a, const T& b) {
return a / b;
}
// 3) Enabled only if T does NOT support division
template <typename T, std::enable_if_t<!has_division<T>::value, int> = 0>
void divide(const T&, const T&) {
std::cout << "No division available for this type\n";
}
int main() {
std::cout << divide(10, 2) << "\n"; // OK
std::cout << divide(10.0, 2.0) << "\n"; // OK
std::string s1 = "a", s2 = "b";
divide(s1, s2); // graceful fallback
}You’ll see SFINAE used to:
- Enable algorithms only for numeric types
- Prevent misuse of generic APIs
- Create type-safe libraries (Eigen, STL, ranges, fmt, etc.)
- Provide better overload selection instead of runtime checks
Think of SFINAE like this:
“If this template substitution doesn’t make sense, pretend this function never existed.”
No error. No warning. Just… gone.
If asked:
“What is SFINAE and why do we need it?”
Answer:
SFINAE lets template overloads disappear when a type does not satisfy certain compile-time requirements, enabling safe, expressive, and type-checked generic APIs without runtime overhead.