Module 12 — Exercises¶
Every exercise states its cognitive level, its task, and the observable result that counts as success. Work in a scratch directory and compile with the course's flags, treating a warning as a failure:
Warm-up [L1 Understand]¶
W1 — One function template, three types¶
Task. Write a function template smallestOf that returns the smallest of three values. Use it
with int, std::string and double, and then write down what the compiler generated for each
call.
Success looks like. Three calls that compile and print the right answer, plus a sentence per
call naming the instantiation (smallestOf<int>, smallestOf<std::string>, smallestOf<double>) —
and one sentence saying what requirement the template imposed on its type.
Stuck? Section 2 of Concepts.
W2 — Predict the failure before running it¶
Task. This template works for int and fails for Book. Without compiling, say which
requirement is missing, then confirm it with the compiler.
struct Book { std::string title; int copies = 0; };
template <typename T>
T bestOf(const std::vector<T>& values) {
T best = values.front();
for (const T& value : values) { if (value > best) { best = value; } }
return best;
}
Success looks like. A predicted requirement (operator> for T), followed by the real message,
which on this toolchain is:
and one sentence saying why the element type — not the template — is the thing to change.
Stuck? Section 3 of Concepts.
W3 — Why does this not link?¶
Task. Given a template declared in a header and defined in a .cpp, predict whether the failure
appears at compile time or link time, and say why. Then reproduce it and inspect the object files.
Success looks like. The correct stage (link), the reason (the definition must be visible where it is instantiated), and the evidence:
Stuck? Section 5 of Concepts, and Mistake 1 in Misconceptions and mistakes.
W4 — Balance the two abstractions¶
Task. For each requirement, choose a template or a virtual interface, and give the reason in one sentence:
- A drawing holds circles and rectangles, and a new kind of shape is added each semester.
- A matrix class whose element type is
doubleorint, fixed at compile time. - A report exporter that supports the formats installed on the machine at run time.
- A container of readings, sorted by a key the caller chooses.
- A retry helper wrapping any callable that takes no arguments and returns
bool.
Success looks like. Five answers, each naming why — "the set is known at compile time" or "the set is discovered at run time" — rather than which one sounds better. Answers 1 and 3 are the virtual cases; 2, 4 and 5 are the template cases, and 4 needs a reason about caller-chosen behaviour rather than type.
Stuck? Section 7 of Concepts.
Core [L2 Apply] · [L3 Modify] · [L4 Analyze]¶
C1 — Fix the template that does not link [L3 Modify]¶
Task. Take the split template from W3, make it link, and explain the rule it broke. Then do it the other way, and say when that alternative is the right choice.
Success looks like. Both arrangements building: the definition moved into the header, and the
extended version with template class Repository<int>; at the bottom of the source file. Plus two
sentences: the header version is the default; the explicit-instantiation version trades flexibility
for build time when you know the few types you support.
Stuck? Mistake 1 in Misconceptions and mistakes.
C2 — Add the typenames [L2 Apply]¶
Task. This function does not compile. Fix it, and explain each addition.
template <typename Container>
void summarise(const Container& items) {
typename Container::value_type running{}; // is the typename needed here?
for (const typename Container::value_type& item : items) { // and here?
running = running + item;
}
}
Success looks like. A version that compiles, plus a rule in your own words: a name of the form
X::y where X depends on a template parameter is a type only if you say typename, because the
compiler cannot tell a type from a static value until X is known. One of the two typenames is
required and the other is optional-but-clear; say which is which.
Stuck? Mistake 2 in Misconceptions and mistakes.
C3 — Identify and document the implicit requirement [L4 Analyze]¶
Task. This template works for int and fails for Reading. Find the requirement it was
implicitly imposing, document it in the header, and then either constrain it or generalise it — your
choice, with a reason.
struct Reading { std::string sensor; double value = 0.0; };
template <typename T>
T largestOf(const std::vector<T>& values) {
return *std::max_element(values.begin(), values.end());
}
Success looks like. A written requirement list (copy-constructible, and ordered by operator<
or by a supplied comparator), the static_assert or comparator-overload form you chose, and a
sentence saying why you chose it. The generalised form is usually the better answer here, because the
requirement is exactly what std::max_element already allows you to supply.
Stuck? Section 3 of Concepts.
C4 — Reduce a real error storm [L4 Analyze] (the deliberate failure)¶
Task. Force a template error from inside the standard library — the honest way is to put a
type with no valid ordering into a std::map, or a type that is not movable into a std::vector
element slot — and then:
- capture the whole message,
- reduce it to at most five lines that still fail,
- quote the line that names the cause,
- write one sentence saying what the library was trying to tell you.
Success looks like. All four artefacts, with the reduction actually compiling-failing on its own — not a copy of the original. The quoted line should be a statement about your type, which is the lesson: the library never had a problem.
Stuck? Section 8 of Concepts, and the worked diagnosis in Debugging.
Challenge [L4 Analyze] · [L5 Design] · [L6 Build]¶
X1 — Compare the two abstractions in two directions [L4 Analyze]¶
Task. Build the same extension point twice: once as a class template, once as a virtual interface. Then name one thing each makes easy and one thing each makes impossible, with a stated preference for a stated context.
Success looks like. Both versions working, and four specific claims. For example: the template
works with int and is inlined away, but cannot accept a type written after the program was
compiled; the interface accepts a plugin found at run time, but costs a dispatch per call and cannot
hold a primitive. Then a preference with a context — "for a format converter configured at run time,
the interface; for a matrix type fixed at compile time, the template".
How you will be judged. On the four claims being specific and verifiable, and on the preference naming a context. "Neither is better" is only acceptable when followed by the criterion that decides.
X2 — Build a generic component with stated requirements [L6 Build]¶
Task. Build a small generic component of your own — a fixed-capacity ring buffer, or a key–value
store — and make it work for two unrelated types. Its requirements must be documented, and where
a requirement can be checked at compile time, it must be enforced with a static_assert that names
it in one line.
Success looks like.
- The component working for both types, from the same source, with the outcome compared side by side.
- A requirement list in the header — what
Tmust provide, and why each item is required — that is neither too strong (constraints nothing uses) nor too weak (it breaks for a type it should serve). - At least one
static_assert, and its message quoted from a real failed build. - A recorded note on what the compiler generated for each instantiation, and one sentence on the compile-time cost of the second one.
How you will be judged. On the requirement list. A component that works for both types because nobody wrote down what it needs has not been generalised; it has been tested twice.
X3 — Argue against generalising [L8 Evaluate]¶
Task. Take one of your own components — ideally something you would naturally make a template — and write the case against making it generic. Then decide, with a criterion.
Success looks like.
- A concrete component, ideally from your own Lab 11 or Lab 12 work, with what generalising it would require.
- The costs, named specifically: the requirements that would have to be documented, the callers that would need to change, the error messages that would get harder to read, the compile time, the code size.
- The counter-argument for generalising, stated fairly — because sometimes generalising wins.
- A decision with the criterion that decided it: how many genuine callers exist now, or is the type a fact about the design or something the program discovers, or does the requirement constrain the type or merely describe it?
- One sentence on what would change your mind: the second caller arriving.
How you will be judged. This is the counterweight to the whole module. A student who generalises everything has learned the syntax and not the judgement, and this exercise is the check.
Stretch (optional)¶
- S1 — "What changes if…"
FixedArray's capacity parameter becomes a defaulted second parameter,std::size_t Capacity = 8? What new problems does the default create for a class template used without arguments? - S2 — "What changes if…"
Repository<T>::wherereturnsstd::vector<std::reference_wrapper<const T>>instead ofstd::vector<T>? What does it stop copying, what does it stop being able to do, and which of Module 9's collection-exposure rules does it run into? - S3 — "What changes if…" you add
template class Repository<std::string>;at the bottom of a.cppfor a type that does not meetsortedById's requirement? Predict the error, then compile.
Solutions¶
There are no separate solution files for these exercises. Each is a template to write,
and the reference material is the module's own programs in examples/module-12/:
01-function-templates/ and 02-class-templates/ for the mechanics,
03-non-type-parameters/ and 04-specialisation-and-traits/ for what a template may
require of a type, and 05-generic-repository/ for the class Lab 12 builds on.
Every exercise also names the Concepts section to reread when you are stuck. Attempt first: reading a reference before attempting destroys its value, because you will recognise it later and mistake that for understanding.
Next: Lab 12 — one repository, several kinds of record, with its requirements in writing.