Skip to main content
๐Ÿ“œ WAYPOINT LESSON

Subsumption: Overloads That Pick Themselves

โญโญโญ advancedโณ 10 min read๐Ÿ“ Lesson 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

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