Worked examples — Templates — function templates, class templates
Prerequisites you may want open: Function Overloading, Type Deduction (auto), Compile-time vs Runtime, Template Specialization, Macros vs Templates, Generic Programming, STL Containers.
The scenario matrix
Templates don't have "quadrants" like an angle problem — but they do have a fixed set of situations that behave differently. Here is every cell we must cover.
| # | Case class | The question it tests | Covered by |
|---|---|---|---|
| A | Function template, clean deduction | Compiler reads args, finds ONE T |
Ex 1 |
| B | Deduction conflict (degenerate input) | Two arg types → no single T |
Ex 2 |
| C | Explicit type argument | You override deduction with <...> |
Ex 3 |
| D | Class template, explicit <T> |
No constructor deduction (pre-C++17) | Ex 4 |
| E | Non-type parameter (compile-time value) | A number, not a type, in the <> |
Ex 5 |
| F | Full specialization (the "wrong for one type" case) | Override the generic recipe | Ex 6 |
| G | The linker trap (definition in .cpp) | Real-world debugging word problem | Ex 7 |
| H | Exam twist: overload vs template resolution | Which function actually gets called? | Ex 8 |
Each example below is tagged with its cell letter.
Ex 1 — Cell A: function template, clean deduction
Forecast: Guess T for each call and the returned value before reading on.
- Look only at the argument types.
3and7are bothint. Why this step? Deduction ignores the values — it matches the types of the arguments to the patternT a, T b. Both slots areint, so there is exactly one candidate:T = int. - Instantiate. The compiler stamps out
int maximum(int, int). Why? Only now does real machine code exist — an unused template compiles to nothing (Compile-time vs Runtime). - Evaluate the body.
(3 > 7) ? 3 : 7→7. Why? The ternary picks the larger. - Second call:
2.5,1.1are bothdouble→T = double, returns(2.5 > 1.1) ? 2.5 : 1.1 = 2.5.
Verify:
maximum(3,7)gives7(anint);maximum(2.5,1.1)gives2.5(adouble). Sanity check: the answer is always one of the two inputs, and it's the bigger one. ✓
Ex 2 — Cell B: deduction conflict (degenerate input)
Forecast: Does it return 3, 3.0, or refuse to compile?
- Match arg 1 to the pattern:
3isint→T = int. Why? Same deduction rule as before, applied to the first slot. - Match arg 2 to the pattern:
2.5isdouble→T = double. Why? The second slot is alsoT, so it demandsT = double. - Reconcile.
Tmust be one type, but step 1 saysintand step 2 saysdouble. Contradiction → compile error (not a runtime error). Why this step? Template argument deduction never inserts conversions; it needs an exact type match in every slot that usesT. - The fix — force one
T:maximum<double>(3, 2.5). NowT = doubleis fixed, and3converts to3.0on the way in. Returns3.0. Why the conversion is now allowed? OnceTis pinned by hand, the args are just being passed to a knowndoubleparameter — ordinary implicit conversion applies.
Verify:
maximum(3, 2.5)→ does not compile.maximum<double>(3, 2.5)→3.0, adouble. Sanity:3.0 > 2.5, so the larger,3.0, is returned. ✓
Ex 3 — Cell C: explicit type argument that changes the answer
Forecast: Are both 3.5? Or does one truncate?
half(7): deduceT = int(arg isint). Why? Clean single-type deduction (Cell A machinery).- Body with
T = int:7 / 2is integer division →3. Why this step?2written in the body is anint;int / intin C++ discards the remainder. The template didn't change arithmetic rules — it just choseint. half<double>(7):Tforced todouble; the7converts to7.0. Why? Explicit<double>overrides deduction, exactly like Ex 2's fix.- Body with
T = double:7.0 / 2→3.5. Why?double / intpromotes2to2.0, so real division happens.
Verify:
half(7) == 3(integer division),half<double>(7) == 3.5. The two differ because of the chosenT, not the algorithm. ✓
Ex 4 — Cell D: class template needs explicit <T>
Forecast: Does the constructor's 42 tell the compiler T = int the way maximum(42) would?
- Recall the difference: function templates deduce
Tfrom arguments; class templates (before C++17) do not deduce from a constructor call. Why? A function is the thing you call, so its args are visible at the call. A class nameBoxmust be a complete type before any constructor is considered — the type has to be known first. So you must name it:Box<int>. - Write
Box<int> b(42);. The compiler generates a concrete classBox<int>where everyTbecomesint. Why? Instantiation replaces the parameter throughout the class body. - Call
b.get(): returns the storedvalue, which is42. - Note the type identity:
Box<int>andBox<double>are unrelated types — you cannot assign one to the other. This is the same rule that makesvector<int>andvector<double>different (STL Containers).
Verify:
Box<int> b(42); b.get()→42.Box b(42);fails to compile pre-C++17. ✓
Ex 5 — Cell E: non-type template parameter
Forecast: Is N a variable you can change at runtime, or is it baked in?

- Identify the parameters:
Tis a type,Nis a value — specifically a compile-timeint(Compile-time vs Runtime). Why this step? Templates accept two flavours of parameter.Nis not a type, so it fills theint Nslot with an actual number. Look at the figure: the violet<double, 5>fills both slots of the cutter — one with a type, one with a number. - Instantiate
Array<double, 5>. Nowdatais literallydouble data[5]— a fixed-size array, size known before the program runs. Why it matters? Because5is a compile-time constant, the array sits on the stack, no heap allocation, and the compiler can inline everything. - Call
a.size(): returnsN, which the compiler already replaced with5. This is a compile-time constant, effectivelyreturn 5;. - Degenerate check — could
Nbe0?Array<double, 0>is legal-ish but a zero-length array is a corner case (technically ill-formed for a plainT data[0]); avoid it. This is the "zero input" cell of our matrix.
Verify:
a.size() == 5, storage isdouble[5]on the stack (5 * 8 = 40bytes on a typical 8-byte-doubleplatform). ✓
Ex 6 — Cell F: full specialization (generic recipe is wrong for one type)
Forecast: Do both print "1"? Or does bool behave differently?

Printer<int>::show(1)uses the generic template:to_string(1)→"1". Why this step? There is no special version forint, so the compiler falls back to the general cookie-cutter.Printer<bool>::show(true)— the compiler sees the exact-match specializationPrinter<bool>and prefers it over the generic one. Why? The most specialized matching template wins (Template Specialization). In the figure,boolfollows the orange arrow to its own dedicated recipe.- Run the specialized body:
true ? "true" : "false"→"true". Why we specialized? The genericto_string(true)would give"1"— technically correct but not the human-readable word we wanted. Specialization fixes the one type that misbehaves without touching the others.
Verify:
Printer<int>::show(1) == "1",Printer<bool>::show(true) == "true",Printer<bool>::show(false) == "false". ✓
Ex 7 — Cell G: the linker trap (real-world word problem)
Forecast: Is this a syntax bug, a runtime crash, or a linker problem? Guess before reading.
- Where is
Stack<int>first used? Inmain.cpp. Why this step? Instantiation happens at the point of use. The compiler compilingmain.cppis the one that must generateStack<int>::push. - What can the
main.cppcompiler see? Onlystack.h(the declaration). The body ofpushlives instack.cpp, a separate translation unit (Compile-time vs Runtime). Why it matters? Without the body, the compiler emits a call toStack<int>::pushbut never generates the function itself — it assumes some other unit will. stack.cppnever instantiatesStack<int>(it never mentionsint), so nobody generates the code → the linker finds a dangling reference →undefined reference. Why "linker" and not "compiler"? Each file compiled fine on its own; the mismatch only shows when linking the object files together.- The cure: put the full template definition in the header (
.h/.hpp), so every file that uses the template sees the body and can instantiate it. Why this is safe from multiple-definition errors? Template instantiations get special linkage — identical instantiations across units are merged, not duplicated.
Verify (conceptual): move the body into
stack.h→main.cppsees it →Stack<int>::pushis generated → link succeeds. The class of bug is a linker error, confirmed by theundefined referencewording. ✓
Ex 8 — Cell H: exam twist — overload vs template resolution
Forecast: Three calls, three answers. Guess each.
who(5): an exact non-template matchwho(int)exists. Why this step? When a normal (non-template) function is an equally good match, the compiler prefers the non-template overload (Function Overloading). So it returns"plain int".who(2.5):the plainwho(int)would require adouble → intconversion (a worse match), butwho(T)withT = doubleis an exact match. Why? The template gives an exact type match; exact beats a conversion. Returns"template".who<int>(5): the<int>explicitly demands the template withT = int, so the plain overload is not even considered as the primary — the template wins. Returns"template". Why? You named a template argument, which only a template can accept, so overload resolution is forced onto the template path.
Verify:
who(5) == "plain int",who(2.5) == "template",who<int>(5) == "template". Sanity: exact-match plain function beats template for5; template wins for2.5(avoids a narrowing conversion) and whenever you write<...>. ✓
Recall Quick self-test
Why does maximum(3, 2.5) fail? ::: Deduction gives T=int from arg 1 and T=double from arg 2 — one T can't be both.
half(7) returns? ::: 3, because T=int makes 7/2 integer division.
half<double>(7) returns? ::: 3.5, because T=double makes it real division.
Why must Box b(42); be Box<int> b(42); pre-C++17? ::: Class templates don't deduce T from a constructor; the type must be named first.
Printer<bool>::show(true) returns? ::: "true", from the full specialization that overrides the generic recipe.
The linker error undefined reference for a template means? ::: The definition sat in a .cpp the using file couldn't see; put the full body in the header.
who(2.5) returns? ::: "template" — exact template match beats a double→int conversion into the plain overload.