Skip to content

Module 12 — Templates

After this module you can write code that works for a type you do not know yet, and you can explain why that is a different tool from polymorphism — with different costs.


At a glance

Prerequisites Module 11
Estimated study time 8–10 hours
Lab Lab 12 — a generic repository type used for two record kinds
Exercises Three tiers — warm-up, core, challenge
Cognitive levels L4 Analyze → L6 Build — exit level L6 Build
Self-assessment Module quiz

Why this module matters

The running system contains a Library that holds Book objects. Now suppose it must also hold Member objects, Loan records, and eventually items from other parts of the campus. Written three times, that container is three near-identical classes that must all be fixed whenever the container logic improves.

A template writes it once, in terms of a type that is supplied later. The compiler then generates a version for each type actually used — so you write one class and get several, each as efficient as if it had been hand-written.

That is a genuinely different mechanism from the polymorphism of Module 7, and choosing between them is a design decision you will be able to make on the evidence:

Templates Virtual functions
Decided when Compile time Run time
Cost at run time None extra A level of indirection per call
Types must be known At compile time Only the base type
Works for built-in types Yes No
Recompilation Changing the template recompiles its users Changing an implementation need not

The standard library is written in templates, so this module is also the door into the STL — which is why Module 14 follows immediately after.

Honest warning: template error messages are the worst in C++. This module treats that as a skill, with a method for reading them.


Learning objectives

By the end of this module you will be able to:

  1. Write a function template and explain how the compiler deduces the template arguments from the call.
  2. Write a class template with a well-defined interface, and instantiate it for more than one type.
  3. Explain why template definitions are usually placed in headers, and what that means for compilation and for build times.
  4. Instantiate a template explicitly where that is the better choice, and explain when.
  5. Use non-type template parameters, and say what they are for.
  6. Explain template specialisation at a high level, and describe why type traits exist.
  7. Compare generic programming (templates) with dynamic polymorphism, and choose between them for a stated requirement.
  8. Use auto and decltype appropriately, and explain what auto does and does not deduce.
  9. Write a class template constructor taking std::initializer_list<T> where that improves usability.
  10. Recognise code bloat and long compile times as consequences of template instantiation, and mitigate them.
  11. Read a long template error message: find the innermost note:, locate your own file name, and fix one error at a time.
  12. State at least one thing C++20's concepts improve about template programming, without depending on them.

Core concepts

  • Template — a compile-time blueprint for a function or class, parameterised over types (or values).
  • Function template — one definition that works for many argument types.
  • Class template — one class definition parameterised over a type; the basis of std::vector, std::map, std::unique_ptr.
  • Template parameter — a placeholder filled in at instantiation, e.g. T.
  • Deduction — the compiler working out T from the arguments of a call.
  • Instantiation — generating real code for a specific set of template arguments. Happens at compile time.
  • Why headers — the compiler must see the template's definition, not just its declaration, at every point of use, so definitions normally live in headers.
  • Explicit instantiation — asking for a specific instantiation in one translation unit, so the definition can live in a .cpp. Useful to reduce build times when only a few types are ever used.
  • Non-type parameter — a template parameter that is a value, not a type, e.g. a fixed size: template <typename T, int Size>.
  • Variadic template — a template taking any number of arguments; how make_unique and make_shared forward their arguments. Recognised, not required.
  • Specialisation — providing a different implementation of a template for a particular type. std::vector<bool> is the famous odd case.
  • Type trait — a small template that answers a compile-time question about a type (is it an integer? is it copyable?). Used by the standard library to choose between implementations, and by if constexpr.
  • if constexpr (C++17) — compile-time branching inside a template, so unused branches need not even compile for a given type.
  • Concept (C++20) — a named requirement on a template parameter, which produces far better error messages. Worth knowing, not required here.
  • Code bloat — each instantiation is a separate function or class. Templates used with many types produce more code.
  • Template alias — using IntRepository = Repository<int>;; a readable name for a specific instantiation.
  • Generic programming — writing algorithms in terms of requirements rather than concrete types. The design philosophy of the STL.

Module contents

Section What it contains
Introduction (this page) Overview, prerequisites, objectives, concept inventory
Concepts and explanations The duplication and the three answers to it; function and class templates; requirements and static_assert; instantiation and why the definition goes in a header; non-type parameters; reading a template error; specialisation and if constexpr; when not to generalise
Worked examples Six programs — one definition for three types, one class for four element types, a capacity as part of the type, a full specialisation and the standard library's proxy, a requirement the compiler can enforce, and one repository serving two record kinds
Code examples examples/module-12/ — five programs, each with captured output
Misconceptions and mistakes Eight wrong mental models with the program that contradicts each, ten symptoms, and five worked mistakes with their real compiler and linker messages
Debugging Reduction as the method: the last line first, the frame naming your own file, five lines that still fail — and a diagnosis that started inside the standard library
Exercises Three tiers, from one template used with three types to a generic component with enforced requirements, plus the required error reduction
Lab 12 Three containers become one Repository<T>: requirements documented in the header, a requirement failure forced and reduced, two unrelated instantiations
Challenge problems Generalise a component and state its requirements, design an iteration interface, and argue that something should not be a template
Self-assessment Nine questions, including a prediction about compile time versus link time
Summary What must be retained, the terms to be able to define, and the checklist
Further reading Books and official documentation

What you can do after this module

  • Write a function template and a class template from scratch, with a documented interface.
  • Decide between a template and a virtual interface for a stated requirement, and defend the choice.
  • Explain why a template definition in a .cpp file produces link errors, and fix it.
  • Diagnose a templated compile error instead of abandoning the approach.
  • Recognise every standard container you use as an instance of a class template you now understand.

Where this is used later

  • The entire standard library is templates; Module 14 uses this module's material directly.
  • if constexpr and type traits appear in the capstone helper code in Module 16.
  • Exceptions thrown from template code require care in Module 13, especially for noexcept move operations inside containers.
  • Compile-time vs run-time decisions are revisited as the strategy pattern in Module 15 — the run-time twin of a template parameter.

Common mistakes to watch for

Symptom Cause
undefined reference to 'Repository<int>::add(int)' The template's definition is in a .cpp file and the compiler could not see it at the point of use. Move it to the header, or instantiate explicitly
Hundreds of lines of error, none of them in your own file The instantiation failed inside a standard algorithm; find the innermost note: and the required from here line
error: no matching function for call to 'max(int, double)' Template argument deduction produced conflicting types (int and double). Make the types agree, or take two parameters
error: 'T' does not name a type inside a template A dependent name needs typename — for example typename Container::iterator
error: no match for 'operator<' The template requires something of its type that the type cannot do: here, an ordering. This is the moment C++20 concepts would have said so clearly
Build times grow sharply Many instantiations, or heavy headers. Prefer explicit instantiation, and reduce what headers include
A template used with only one type Possibly a missing abstraction, or possibly YAGNI. Add generality when a second type actually appears

Skills checklist

Levels this module works at: [L4 Analyze → L6 Build] — see How you will learn.

Tick an item only when the evidence exists:

  • [ ] I can write and use a function template — evidence: one template used with three types, with the instantiations named
  • [ ] I can write a class template used with two unrelated types — evidence: Lab 12's Repository<T> in use for two record kinds
  • [ ] I can state a template's requirements — evidence: the documented requirement list in the header, with each item justified by something in the body
  • [ ] I can explain instantiation and why the definition goes in a header — evidence: the link failure reproduced, and the object files inspected to find the missing symbol
  • [ ] I can use typename where it is required — evidence: exercise C2, with each addition justified by a dependent name
  • [ ] I can read a long template error and reduce it — evidence: Lab 12's recorded reduction, five lines, with the causal line quoted
  • [ ] I can compare a template design with a virtual design and choose — evidence: exercise X1's two-direction comparison, with the preference tied to a context
  • [ ] I can argue against generalising when it is not warranted — evidence: challenge 3's judgement, with the rejected option's cost named

Suggested next step

Previous: Module 11 — RAII and Smart Pointers · Next: Module 13 — Exception Handling · All modules: Modules index

Module 13 assumes your resources are owned by types, because that is the only reason its failure paths can be trusted, and it uses your generic components: an exception thrown while a Repository<T> is part-way through an operation must leave the repository consistent, and that is exactly what the exception-safety guarantees are for.