5.2.15 · D3C++ Programming

Worked examples — Template specialization — full and partial

4,480 words20 min readBack to topic

This page assumes you have met the basic template syntax. If template<class T> still looks alien, read that first.


Two words you need first: "pattern" and "shape gate"

Before any example, let us nail down two words we use constantly.

The figure below draws the doorway metaphor literally — keep it in mind, because every example on this page is just "which gates does X walk through?"

Figure — Template specialization — full and partial

Look at the three doorways. Each is cut into the shape of a pattern: the widest opening is T (the primary — any type fits), the medium one is T* (only pointer-shaped types fit), and the narrowest is int* (only that exact type fits). Watch the two travellers: the yellow int block is not pointer-shaped, so it can only walk through the widest T door. The pink int* block is pointer-shaped, so it fits through all three doors — and the compiler always sends it through the narrowest door it fits, which is int*. That "narrowest door wins" is the whole selection rule in one picture.

Keep the two words — pattern and shape gate — in mind: every example below is just "which patterns' gates does X pass through, and which survivor is most specialized?"


The scenario matrix

Every specialization question is really "which candidate wins for type X?" The candidates are: the primary (the generic fallback), a full spec (all parameters pinned to concrete types), and one or more partial specs (shape constrained, some parameters still free). The type X you feed in is the input; the winning candidate is the output.

The map below lays out every case class as a landscape of shape gates rather than a wall of text. Read it as a decision fan: an input X enters at the left and lands in exactly one cell depending on how many gates it fits and how those gates relate.

Figure — Template specialization — full and partial

Each labelled node in the figure is a cell of the matrix — a distinct scenario the topic can throw at you. The colour tells you the outcome (white = primary fires, blue = a partial fires, yellow = a full spec fires, pink = an error). The tiny tag under each node (Ex 1, Ex 2, …) points to the worked example that drills it. The cells are:

  • Ano spec matches → the primary fires (the fallback). (Ex 1)
  • B — a full spec matches → it beats everything, because X is exactly the pinned type. (Ex 1, Ex 8)
  • C — a partial matches and the primary also matches → the partial wins. (Ex 2)
  • Done parameter pinned, another still free (mixed concrete + free). (Ex 3)
  • Etwo candidates compete, one is strictly more specialized → it wins by subset ordering. (Ex 4)
  • Ftwo partials tie, neither a subset of the other → ambiguity error (the degenerate case). (Ex 5)
  • Gnested shape (T**, pointer-to-pointer) → the most-nested pattern wins. (Ex 6)
  • Hzero / degenerate input such as void and void* (a boundary type). (Ex 7)
  • I — a real-world word problem: a per-type serializer. (Ex 8)
  • J — the exam twist: trying to partially specialize a function → use overloading instead. (Ex 9)
  • Klimiting behaviour: reference vs pointer vs array shapes are all distinct, each firing its own partial. (Ex 10)

Every worked example below is tagged with the cell(s) it covers. Together they fill the whole map.


The one picture to hold in your head

Figure — Template specialization — full and partial

Read the figure top to bottom: a type X drops in (top, pale-yellow box), the compiler collects all candidates (white box), throws out the ones whose pattern fails the shape gate (pink box — "discard those whose pattern does not match X"), then among survivors picks the most specialized (blue box). The three outcome boxes at the bottom — primary / partial / full — are the three colours you'll see repeated in every example: white for the fallback, blue for a partial, yellow for a full spec.


Worked Examples



Recall

Recall For

int**, rank which of T, T*, T** wins and why All three match; T** is a strict subset of T*, which is a strict subset of T. ::: T** wins — it is the most specialized (deepest nesting = strongest constraint).

Recall Does

V<void*> fire the T* partial? Why or why not? Specialization matches type structure, not usability. ::: Yes — void* has pointer shape (T* with T=void), so the partial fires even though you can't dereference void*.

Recall Why is

Tag<const char*> tagged "cstr" not "ptr:cstr"? Because a full specialization outranks a matching partial. ::: The full spec Tag<const char*> beats the partial Tag<T*>, so it never recurses into the pointer branch.

Recall Ex 5 gives an ambiguity error. What single line fixes it?

Add something strictly more specialized than both partials. ::: template<> struct M<int,int>{}; — a full spec beats both partials, breaking the tie.

Recall For

int[4] and int&, why is there never an ambiguity between the array and reference partials? Their shape gates are disjoint. ::: A reference is not an array and an array is not a reference, so each input opens exactly one gate — only one partial ever survives.

Related deep tools worth a visit: SFINAE and enable_if, constexpr if, and the container quirk std::vector<bool> special case.