Module 12 — Self-Assessment¶
Nine questions. Part A checks vocabulary; Part B checks that you can predict — which, in a module whose errors appear late and whose failures are silent, is the only part that tells you whether you can use this material. Answer on paper, then mark yourself and note which section of Concepts each miss points back to.
Part A — Recall and comprehension¶
-
What is a template, in one sentence? (See section 2.)
-
When are the two phases of template checking, and what is checked in each? (Sections 3, 4 and 5. The second phase is why "it compiles" means very little for a template.)
-
Why does a template's definition normally live in a header? What is the alternative, and what does it cost? (Section 5.)
-
Name one thing a template can do that a virtual function cannot, and one it cannot do that a virtual function can. (Section 7.)
-
When is
typenamerequired, and why can the compiler not work it out? (Section 8.)
Part B — Prediction and explanation¶
- Predict what happens, and at which stage — compile time or link time.
// container.h
template <class T>
class Container {
public:
void add(T value);
};
// container.cpp
template <class T>
void Container<T>::add(T value) { /* ... */ }
// main.cpp
#include "container.h"
int main() { Container<int> c; c.add(1); }
Answer. It compiles cleanly and fails at link time. The compiler is happy because main.cpp
sees a valid declaration; the definition is what it needs to generate Container<int>::add, and
that definition is in a translation unit nobody instantiated. On this toolchain the linker reports
only collect2.exe: error: ld returned 5 exit status — no symbol name — and the evidence has to come
from the object files:
$ nm -C main.o | grep ' U ' | grep Container
U Container<int>::add(int)
$ nm -C container.o | grep -c Container
0
main.o asks for a symbol that container.o does not define, because nothing in container.cpp
caused an instantiation.
A full-credit answer names both fixes and the trade between them: put the definition in the header
(the default — every user can instantiate, at the cost of recompiling all of them whenever it
changes), or keep it in a .cpp with template class Container<int>;, which makes every new type a
deliberate edit and shortens users' builds.
- Predict the message, and say what the compiler is telling you.
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());
}
Answer. The instantiation fails inside <algorithm>, and the last line of the diagnostic says:
The compiler is telling you about your type, not about std::max_element. The template imposed a
requirement — an ordering — that was never written down, and Reading does not meet it. int met it
incidentally, which is why the same code compiled a minute earlier.
A full-credit answer describes the shape of the diagnostic as well: an In instantiation of … [with T
= Reading] line, a required from here frame naming your file, and the real complaint last. It
also gives a fix and says which kind it is — either give Reading a coherent operator< (Module 5's
strict weak ordering, not <=), or generalise the template to take a comparator so the requirement
becomes the caller's business, which is what the standard library does.
- Predict the counters, and explain the code you would expect to be generated.
template <typename T>
class Stack {
public:
Stack() { ++defaultConstructions(); }
static int& defaultConstructions() { static int count = 0; return count; }
private:
std::vector<T> items_;
};
Stack<int> a;
Stack<int> b;
Stack<std::string> c;
std::cout << Stack<int>::defaultConstructions() << ' '
<< Stack<std::string>::defaultConstructions() << '\n';
Answer. It prints 2 1. Stack<int> and Stack<std::string> are distinct types, each with
its own instantiation of every member — including the function-local static, so
Stack<int>::defaultConstructions() and Stack<std::string>::defaultConstructions() are different
functions reading different variables. Nothing is shared between instantiations.
A full-credit answer adds the consequence the counters are measuring: Stack<double> copy = someStackOfInt;
does not compile, for the same reason double d = someInt; is a conversion and not an identity — two
instantiations have no relationship at all, not even a common base class. It also notes that the
compiler generated two complete classes here, and that a member function which only makes sense for
some element types is compiled when it is used, not when the class is instantiated.
- Explain a design decision. A colleague proposes making every container in your library generic "so it can be reused". Evaluate the proposal, and give the criterion you would apply instead.
Answer. The proposal confuses the possibility of reuse with its actual existence. Making a component generic has immediate costs: the requirements must be documented, or the second user discovers them as a diagnostic storm; the errors that users see move from their own code into a library header; the header changes recompile every user; each instantiation is another generated class, which costs code size and build time; and — the subtle one — a component used with a single type hides the requirement that mattered, because that type satisfied it incidentally.
The criterion the course applies: generalise when a second genuine use exists now, not in anticipation. Two callers are evidence; "we might need it" is not. The counter-argument also needs stating fairly, because sometimes generalising early is right: when the second use is already scheduled and the interface will not change under generalisation — Lab 12's repository, with a third record kind already built in Labs 9 through 11, is the honest example.
A full-credit answer names what makes a good generalisation, since the decision is not
"template or not": the element type appearing only where it must, the requirements written next to
the declaration, and the ones that can be checked enforced with a static_assert that names them in
one line.
Marking yourself¶
| Score | What it means |
|---|---|
| ≥ 80%, every miss explained | Move on to Module 13 |
| 60–79% | Re-read sections 5 and 8 of Concepts, redo the link-error and reduction exercises, then retake in a week |
| < 60% | Redo Examples 1 and 6 from the worked examples, and Lab 12's requirement failure, before continuing |
Module 14 is the standard library, which is templates, so a vague model here makes that module a list of container names with no reasoning behind them. The single most useful thing to carry forward is the reading method: last line first, find your own filename, reduce to five lines. You will use it in every remaining module, and it is the difference between a template being a tool and a template being a wall.
A quiz is diagnostic, not a grade. Keep the list of misses and reread it before Lab 12.
Next: Summary — the module compressed to what you must retain.