SFINAE (substitution failure is not an error) is the rule that when the compiler substitutes a candidate template's arguments during overload resolution and the substitution produces an invalid type or expression, that candidate is silently removed from the overload set instead of triggering a hard compile error. It's the mechanism that lets library code write multiple template overloads that only “exist” for types satisfying some property, without the caller ever seeing the ones that don't apply.
template <typename T> auto has_size(int) -> decltype(std::declval<T>().size(), std::true_type{}); template <typename T> auto has_size(...) -> std::false_type; static_assert(decltype(has_size<std::vector<int>>(0))::value); // true static_assert(!decltype(has_size<int>(0))::value); // false
Ordinarily, referencing a nonexistent member (.size() on a type that has none) is a hard compile error. Inside a template's immediate context during argument deduction, though, an invalid expression just disqualifies that overload from consideration, as if it had never been written, and the compiler moves on to try the other candidates. This only applies to the immediate context of substitution: an error that only shows up once inside the function body (after substitution has already succeeded) is a real, hard compile error, not SFINAE. The int vs ... (ellipsis) pair above exploits overload resolution's preference for an exact match, int, over a variadic fallback, so when the decltype expression in the first overload is well-formed, it wins; when it isn't, SFINAE removes that overload and the ... fallback is the only one left.
The classic use is std::enable_if, which turns a template parameter into a “does this substitution succeed” gate: writing enable_if_t<std::is_integral_v<T>> as an extra defaulted template parameter makes an overload disappear entirely for non-integral T, rather than compiling and misbehaving.
template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>> void process(T value) { /* only participates in overload resolution for integral T */ }
Modern code increasingly reaches for if constexpr or C++20 concepts (requires clauses) instead of hand-rolled SFINAE, since both express the same “only valid for types with this property” intent far more readably. SFINAE is still worth understanding on its own terms, though, because it's the mechanism enable_if, void_t-based detection idioms, and concepts under the hood are all built from, and a huge amount of pre-C++20 template metaprogramming in existing codebases is written directly in terms of it.