Subsumption: Overloads That Pick Themselves
When one concept implies another, the compiler routes calls to the more specific overload automatically โ design constraints as a hierarchy.
Subsumption in one picture
If concept B's constraints logically include concept A's (SizedIterable = Iterable<T> && has_size), then B subsumes A. Given both overloads, a call satisfying both resolves to the B version โ no tag dispatch, no priority tricks, no ambiguity:
template <Iterable C> std::string category(const C&) { return "iterable"; }
template <SizedIterable C> std::string category(const C&) { return "sized-iterable"; }
category(std::vector<int>{}) picks "sized-iterable"; a begin/end-only range picks "iterable". The compiler knows Iterable && X is more constrained than Iterable because it tracks the constraint expression's structure.
Why this beats tag dispatch
The subsumption graph lives in the types โ a caller cannot pick the wrong overload, and a new level in the hierarchy needs zero call-site changes. tag-dispatch code needs every site updated.
Design pattern: constraint ladders
Order your overloads from most to least constrained and let subsumption route:
SizedRandomAccessโ indexable and sized: O(1) paths,Iterableโ anything with begin/end: linear paths,- unconstrained fallback โ clear error or deferred runtime check.
The graded exercise builds exactly this ladder and asks which rung served the call.