Skip to main content

Subsumption: Overloads That Pick Themselves

advanced10 min readLesson 145 of 204

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:

  1. SizedRandomAccess โ€” indexable and sized: O(1) paths,
  2. Iterable โ€” anything with begin/end: linear paths,
  3. unconstrained fallback โ€” clear error or deferred runtime check.

The graded exercise builds exactly this ladder and asks which rung served the call.

Now practice

Subsumption LaddersConstraint hierarchies that route calls themselves โ€” no tag dispatch needed.1 challenge ยท ยท ~16 min