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:
- Write a function template and explain how the compiler deduces the template arguments from the call.
- Write a class template with a well-defined interface, and instantiate it for more than one type.
- Explain why template definitions are usually placed in headers, and what that means for compilation and for build times.
- Instantiate a template explicitly where that is the better choice, and explain when.
- Use non-type template parameters, and say what they are for.
- Explain template specialisation at a high level, and describe why type traits exist.
- Compare generic programming (templates) with dynamic polymorphism, and choose between them for a stated requirement.
- Use
autoanddecltypeappropriately, and explain whatautodoes and does not deduce. - Write a class template constructor taking
std::initializer_list<T>where that improves usability. - Recognise code bloat and long compile times as consequences of template instantiation, and mitigate them.
- Read a long template error message: find the innermost
note:, locate your own file name, and fix one error at a time. - 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
Tfrom 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_uniqueandmake_sharedforward 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
.cppfile 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 constexprand type traits appear in the capstone helper code in Module 16.- Exceptions thrown from template code require care in
Module 13, especially for
noexceptmove 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
typenamewhere 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.