5.2.18 · D5C++ Programming

Question bank — Concepts (C++20) — constraining templates

1,276 words6 min readBack to topic

True or false — justify

A concept can hold runtime state that changes between calls.
False. A concept is a ==constexpr bool template== fixed entirely by its type arguments at compile time; it has no storage and never executes at runtime.
requires(T a){ a + a; } actually adds a to a when the constraint is checked.
False. The braces test only whether a + a is a well-formed expression; nothing is computed and a is a fictional variable that is never constructed.
If Numeric<T> is false, the template using it produces a compile error at that spot.
False. The constrained template is silently removed from the candidate set; an error appears only if no candidate survives.
concept C = requires(T){ }; with an empty body is always true.
True. An empty requires-expression has no requirements to fail, so the conjunction "every requirement holds" is vacuously true for any T.
std::integral<double> is true.
False. double is a floating type; std::integral matches only integer types like int, long, char, bool — see type_traits — std::integral, std::floating_point.
Two overloads with identical constraints are ambiguous when both match.
True. Subsumption only breaks ties when one constraint is strictly stronger; equal constraints give the compiler no way to prefer one, so it reports ambiguity.
SignedIntegral subsuming Integral means SignedIntegral accepts more types.
False. It accepts fewer types (a stricter set); "subsumes" means "requires strictly more", which is why the more-constrained overload is preferred.
A requires-clause and a requires-expression are the same keyword doing the same job.
False. The clause introduces a constraint (requires Numeric<T>, evaluates a bool); the expression builds a bool by listing testable expressions (requires(T a){ a+a; }).
You can put if (...) { ... } inside a concept definition body.
False. A concept's body must be a single ==constexpr bool expression==; it is a predicate definition, not a statement block.
requires requires is always a typo.
False. It is legal: the first requires is the clause, the second begins an inline requires-expression — requires requires(T a){ a.size(); }.

Spot the error

template<Numeric> T sq(T x){...} — what's wrong?
The constrained-parameter form needs a name: template<Numeric T>. Written as Numeric alone, there is no type name T to use in the body.
concept Even = (T % 2 == 0); — why is this ill-formed?
A concept is a predicate on types, not values; T is a type, so T % 2 is meaningless. Concepts test what operations a type supports, never a runtime value.
requires(T a){ { a.size() } -> int; } — what's the mistake?
A compound requirement's -> must name a type-constraint (a concept), not a bare type. Correct: -> std::convertible_to<int>.
auto f(Numeric x){ return x*x; } — why won't this constrain x?
The abbreviated form needs auto: Numeric auto x. Without auto, Numeric is read as a type name (there is no such type), so it fails to compile for a different reason than intended.
template<typename T> requires Numeric<T> && — the trailing && — is this fine?
No, it is a syntax error: a requires-clause must be a complete constraint expression. But note the valid mistake nearby — requires (A || B) && C needs parentheses because ||/&& in constraints have specific grouping for subsumption.
concept C = std::integral<T> || std::floating_point; — spot it.
std::floating_point is missing its argument <T>. Each atomic concept in the disjunction must be applied to a type.

Why questions

Why do errors from constraints point at the call site instead of deep template guts?
The constraint is checked before the template body is instantiated, so the compiler rejects the type at the point of use rather than failing later inside code you never wrote.
Why prefer a named concept over an inline requires requires?
A named concept is reusable, self-documenting, and participates in subsumption (the compiler can compare named atomic constraints); ad-hoc inline requires-expressions are opaque and cannot be ordered against each other.
Why does overloading on std::integral vs std::floating_point never clash for int?
For int, floating_point<int> is false, so that overload is removed from candidates entirely — only one survives, so there is nothing to disambiguate.
Why did concepts largely replace std::enable_if gymnastics?
Concepts give readable call-site errors and automatic ordering via subsumption, whereas SFINAE and std::enable_if hid the intent in return-type/parameter tricks and produced cryptic "no matching function" walls.
Why must the variables in requires(T a, T b) never actually be constructed?
They are purely fictional stand-ins to test expression validity; if the compiler built them it might need a constructor that the type lacks, defeating the point of only checking well-formedness.
Why is a compound requirement { e } -> C stronger than the simple requirement e;?
The simple form only asks that e compiles; the compound form also constrains the ==type of e== via concept C, catching cases where the expression is valid but returns the wrong type.

Edge cases

What is Addable<void> where Addable = requires(T a,T b){ a+b; }?
false — you cannot declare parameters of type void, so the requires-expression's parameter list is ill-formed and the concept yields false rather than erroring.
Does a concept see private members when testing requires(T a){ a.secret(); } from outside the class?
No. Access rules still apply; a.secret() is not well-formed if secret is private in that context, so the requirement fails just as normal code would.
If a type satisfies both Integral and a user concept PrintableInt that also requires Integral, which overload wins?
PrintableInt, because it requires everything Integral does plus more — it subsumes Integral, so it is the more-constrained, preferred candidate.
Can a concept be recursive (refer to itself)?
No. A concept's constraint must be resolvable without depending on its own satisfaction; self-reference is ill-formed, unlike ordinary recursive templates.
What does the nested requires requires(decltype(*std::begin(r)) x){ std::cout << x; } protect against?
It checks each element is streamable, so a vector<NoStreamType> is rejected cleanly at the call site instead of exploding inside the range-for loop — the Ranges library (C++20) pattern.
When zero overloads survive constraint checking, what message do you get?
The clean "no matching function for call" — which is precisely the desired outcome, since every candidate was silently removed for a stated, checkable reason.

Recall One-line self-test

"Constraint fails" means the template is removed, not erroring — and errors surface only when the whole candidate set is empty. Justify why removal (not error) is the right design. ::: It is what makes concept-based overloading work: each unsatisfied overload steps aside so the correct one is chosen, mirroring how Overload Resolution & Subsumption filters candidates.