cpp core · advanced

Templates: SFINAE, enable_if, Type Traits, if constexpr, Concepts

SFINAE (Substitution Failure Is Not An Error): when substituting template arguments causes a type error in the immediate context, that overload is silently removed from the candidate set. enable_if<cond,T>: condition true → provides ::type=T (overload exists); false → no ::type (SFINAE removes it). Patterns: return type enable_if, or enable_if_t as default non-type parameter. Type traits (<type_traits>): is_integral_v<T>, is_same_v<T,U>, is_trivially_copyable_v<T>, decay_t<T>, remove_reference_t<T>, invoke_result_t<F,Args...>. C++17 if constexpr: single function, compile-time branch; not-taken branch not compiled. Better than SFINAE for most use cases. C++20 Concepts: named constraints; compose with &&/||; clear error messages; template<integral T> void f(T); or requires clause for inline constraints. decltype(declval<T>().method()): test expression validity without construction.

🔑 Key line

SFINAE: ill-formed template substitution removes overload without error; enable_if<cond,T>::type provides the type or fails substitution; C++17 if constexpr replaces most SFINAE; C++20 Concepts are the cleanest solution.

The code

// SFINAE = Substitution Failure Is Not An Error
// When substituting template args causes a type error → skip overload, don't error
// Classic enable_if: enable overload only for integral types
template <typename T>
typename enable_if<is_integral<T>::value, void>::type process(T val) {
cout << "integral: " << val;
}
template <typename T>
typename enable_if<!is_integral<T>::value, void>::type process(T val) {
cout << "non-integral";
}
// Calling:
process(42); // T=int → is_integral<int>=true → first overload ✓
process(3.14); // T=double → is_integral<double>=false → second ✓
// C++17 cleaner: if constexpr
template <typename T>
void process2(T val) {
if constexpr (is_integral_v<T>) {
cout << "integral";
} else {
cout << "non-integral";
}
}
// C++20 cleanest: Concepts
template <integral T>
void process3(T val) {
cout << "integral: " << val;
}
// Type traits cheat sheet:
// is_integral<T> — int, char, bool, long, ... (not float, not class)
// is_floating_point<T>— float, double, long double
// is_class<T> — class or struct (not union)
// is_same<T,U> — identical types (exact match, not convertible)
// is_base_of<B,D> — B is base of D
// is_constructible<T,Args...> — T can be constructed from Args
// is_trivially_copyable<T> — safe to memcpy
// remove_const<T>::type — T without const
// decay<T>::type — remove ref, cv-qualifiers; array→ptr; func→ptr
// invoke_result<F,Args>::type — return type of F(Args...)

What this lesson walks through

  1. 01SFINAE — Substitution Failure Is Not An Error
  2. 02enable_if — the SFINAE workhorse
  3. 03Type traits — compile-time type inspection
  4. 04Modern SFINAE — if constexpr (C++17)
  5. 05C++20 Concepts — the final evolution of SFINAE
  6. 06decltype and declval — SFINAE's best friends

SFINAE: when the compiler substitutes template arguments and the resulting type expression is ill-formed, that substitution fails silently — the overload is removed from the candidate set without issuing an error. If another overload matches, it's used. If none match, THEN it's an error. This enables compile-time dispatch based on type properties.

See it animated — step by step, at your own pace

Unlock the full interactive walkthrough of Templates: SFINAE, enable_if, Type Traits, if constexpr, Concepts and 100+ animated C++ interview lessons.

← Previous
Perfect Forwarding & Reference Collapsing
Next →
Templates: Variadic Templates, Fold Expressions, Parameter Pack Patterns