A concept, declared with the concept keyword, names a compile-time predicate over a type — std::integral, std::copyable, or a hand-written one combining requires clauses. Applying a concept to a template parameter (template <std::integral T>, or the equivalent requires clause) makes the compiler check that constraint immediately at the call site, producing a short, specific error naming exactly which requirement failed, rather than the deeply nested instantiation errors that unconstrained templates historically produced when a mismatched type was used somewhere deep inside the template body.
Using a built-in standard concept
The compiler rejects add("a", "b") immediately with a clear message naming std::integral as the unsatisfied constraint, rather than letting the call proceed into the function body and fail confusingly on the + operator deep inside.
template <std::integral T> // constrains T to integer types only
T add(T a, T b) {
return a + b;
}
add(3, 4); // fine — int satisfies std::integral
// add("a", "b"); // COMPILE ERROR: clearly states std::integral isn't satisfied by const char*Writing a custom concept
requires { ... } checks that the given expressions are valid for T — here, a type qualifies as Printable only if it can genuinely be streamed to std::cout, checked directly rather than assumed.
template <typename T>
concept Printable = requires(T t) {
{ std::cout << t }; // T must support being streamed to std::cout
};
template <Printable T>
void print(T value) {
std::cout << value << "\n";
}requires clauses: an alternative syntax
This form is equivalent to the angle-bracket constraint syntax but reads more like an ordinary if condition, often preferred when combining multiple constraints together.
template <typename T>
requires std::integral<T> && std::signed_integral<T>
T abs_value(T x) {
return x < 0 ? -x : x;
}