Concepts (C++20)
Constraining templates readably - named, checkable requirements on template parameters
- explain why unconstrained templates and old
enable_if/SFINAE tricks produce terrible errors, - apply the standard-library concepts (
std::integral,std::convertible_to,std::sortable, ...), - attach constraints four different ways, including the abbreviated
autoform, - write your own concept with a
requires-expression (simple, compound, type and nested requirements), - use constraint subsumption to pick the most specialised overload.
1. The problem: unconstrained templates and SFINAE
You already know how to write a function template. The trouble is that a bare template<typename T> promises nothing. The type requirements live only inside the body, so a wrong type compiles until the moment some inner operation fails:
template <typename T>
T twice(T x) { return x + x; } // silently assumes T has operator+
twice(std::string{"hi"}); // compiles: "hihi"
twice(std::vector<int>{1,2}); // ERROR - but deep inside <vector>, pages of noise
Before C++20 the fix was SFINAE ("Substitution Failure Is Not An Error") with std::enable_if: you smuggled a constraint into the signature so a bad type made the overload disappear. It worked, but it was write-only:
template <typename T,
typename = std::enable_if_t<std::is_integral_v<T>>>
T twice(T x) { return x + x; } // "only if T is integral" - but who can read that?
The intent ("T must be integral") is buried in template plumbing, the error messages are still awful, and the constraint cannot be reused or named. Concepts replace all of this with a readable, named contract.