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.
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 typestemplate <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 constexprtemplate <typename T>void process2(T val) { if constexpr (is_integral_v<T>) { cout << "integral"; } else { cout << "non-integral"; }}
// C++20 cleanest: Conceptstemplate <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
- 01SFINAE — Substitution Failure Is Not An Error
- 02enable_if — the SFINAE workhorse
- 03Type traits — compile-time type inspection
- 04Modern SFINAE — if constexpr (C++17)
- 05C++20 Concepts — the final evolution of SFINAE
- 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.