C++ for Systems Programmers

SFINAE versus C++20 concepts

/ SFINAE, say 'SFEE-nay'; concepts as written /

When you write a generic function, you often want it to apply only to types that support some operation — say, only to types that have a .size() method, or only to numbers. Without a way to express that, a template will eagerly try to compile for any type and then fail deep inside with a wall of errors. Both SFINAE and C++20 concepts are mechanisms for saying 'this template is only valid for types meeting these requirements' — concepts being the modern, readable replacement for the old, cryptic SFINAE trick.

SFINAE stands for 'substitution failure is not an error'. The idea: while the compiler is trying to instantiate a template and substitutes your type into the declaration, if that substitution produces an invalid type or expression, the compiler does not raise an error — it just silently removes that template from the set of candidates and looks elsewhere. Library authors exploited this to enable or disable overloads based on type properties, typically with std::enable_if, producing powerful but nearly unreadable code. C++20 concepts replace this with named, composable predicates on types: you write a concept like Sortable once, then constrain a template with a requires-clause or by using the concept name in place of class. The compiler checks the constraint up front and, if it fails, tells you plainly that your type does not satisfy the named requirement.

Why it matters: constraints turn 'two hundred lines of template-instantiation gibberish' into 'std::string does not satisfy Number'. Concepts also let the compiler choose the most specialized matching overload cleanly and document a template's requirements in its signature. The honest nuance: concepts do not make templates dynamically typed or add runtime checks — they are purely a compile-time gate, so they cost nothing at run time, and SFINAE still underlies some library internals and older codebases you will encounter. Concepts are the better tool when you have the choice; SFINAE is the thing they were designed to retire.

template<class T> requires std::integral<T> T half(T x) { return x / 2; } // half("hi") fails plainly: 'hi' does not satisfy std::integral

A requires-clause states the type requirement up front, so a bad call is rejected with a clear message instead of deep template errors.

Concepts are a compile-time constraint, not runtime polymorphism: they decide which template is valid before any code runs and add zero runtime cost. They do not replace virtual functions or runtime dispatch — that is a different mechanism entirely.

Also called
substitution failure is not an errorrequires-clausesconstrained templates概念約束模板