SFINAE and Constraints
Choosing overloads with types: enable_if, tag dispatch, the detection idiom, and what concepts fixed
- explain what substitution failure is and precisely where SFINAE applies (the "immediate context"),
- constrain templates with
std::enable_ifon a return type, a template parameter, or a defaulted non-type parameter, - dispatch on type traits at compile time with tag dispatch,
- ask "does this type support X?" with the detection idiom (
void_t), - state the real limits of SFINAE and why C++20 concepts replace most of it.
1. Overload resolution and substitution
You already know that the compiler builds an overload set of candidate functions and then picks the best match. For function templates there is an extra step first: the compiler must substitute the deduced template arguments into the template's signature to form a concrete candidate. SFINAE lives in that substitution step.
When substitution produces an invalid type or expression in the immediate context of the signature - the return type, the parameter list, or the template parameter list - the candidate is not an error. It is simply dropped, and overload resolution continues with whatever candidates remain.
// Two overloads. Substitution decides which one is even *considered*.
template <typename T>
typename T::value_type first(const T& c) { return *c.begin(); } // needs T::value_type
int first(int x) { return x; } // plain fallback
first(std::vector<int>{1,2}); // vector has ::value_type -> template chosen
first(42); // int has no ::value_type -> template silently dropped,
// the int overload is used. No compile error.
The phrase "is not an error" is the whole point: had this been a hard error, adding a template overload could break unrelated calls. Instead, a failed substitution just prunes the candidate list.