Module 12 — Challenge Problems¶
Open problems. More than one defensible answer exists, and the reasoning is assessed as much as the code.
Every one of these is about judgement, not syntax: this module supplies a powerful tool, and the skill being tested is knowing when it is the wrong one. Write your decision and its cost before you compare with anything.
Challenge 1 — Generalise a component of your own, and state what it requires [L6 Build]¶
The problem. Take an ownership-aware component from your Lab 11 work — a container, an index, a loader — and generalise it so that it works for two unrelated types. Then write down exactly what every type must provide.
What a good answer contains.
- A working generalisation, with the same behaviour for both types, demonstrated by the same scripted sequence run twice.
- A requirement list that is neither too strong nor too weak. Too strong: you demanded a default
constructor or an
operator<that nothing in the body uses, and now a perfectly suitable type is refused. Too weak: the component compiles for a type you expected to support and then fails inside a member function at the worst moment. Test the list by trying a third type that should work and a fourth that should not. - At least one requirement expressed as a
static_assert, with its message quoted from a real failing build. - One paragraph on what the generalisation cost: which callers changed, how the errors changed, what happened to compile time, and what the element type now has to be for the component to be useful.
- A sentence naming the requirement you discovered by using the second type — because requirements are found that way, not invented, and the fact that you found one is evidence the exercise worked.
What would make it weak. A generalisation that demands everything the first type happened to
have — copyability, a default constructor, an ordering, a name() — when the body uses only two of
those. That is not a generic component; it is the first type's requirements, renamed.
Deciding it for yourself. For each requirement, remove it and see what breaks. If nothing breaks, it was not a requirement, and demanding it excludes types you meant to support.
Challenge 2 — Design an iteration interface [L5 Design]¶
The problem. A generic container must let callers visit its elements. Design the interface, and justify it against what callers actually need. Choose one primary design, then say what the others would have given you:
(a) begin()/end(), so range-for and every standard algorithm work;
(b) a forEach(callback) that hands each element to a visitor;
(c) index-based access: size() plus at(size_t);
(d) a snapshot() that returns a copy of the elements;
(e) a view type that can also filter and transform.
What a good answer contains.
- The chosen interface, implemented and used by at least two callers with different needs.
- The trade-off named in both directions: for (a), that callers can now mutate through a non-const iterator unless you are careful, and that they depend on your container's iterator type; for (b), that the callback cannot easily stop early or return a value; for (c), that a caller's index can dangle; for (d), that the copy may be expensive; for (e), that the view's lifetime becomes a new question.
- A note on what the standard library's conventions would say, and whether you followed them — the
standard containers all offer (a), which is why every algorithm works with them, and
std::for_eachexists as (b) built on top of (a). - A sentence on which of Module 9's collection-exposure rules your choice respects, and which it deliberately does not.
What would make it weak. Choosing (a) "because the standard library does" without noticing that
the non-const overload hands out mutation — the exact defect Module 9's Example 4 demonstrated.
Also weak: choosing (d) without counting what the copies cost in the caller's real use.
Deciding it for yourself. Ask what a caller must be able to do: read everything, stop early,
transform while reading, or hold the result afterwards. Most real callers need read-and-visit, which
is (a) restricted to const.
This previews Module 14, where the standard algorithms — and what an iterator must provide to work with them — are the subject.
Challenge 3 — Argue that a component should not be a template [L8 Evaluate]¶
The problem. Choose a component that could be generic — your own or one from the course's system — and make the case that it should be one of:
a template, generic over types, decided at compile time
a virtual interface, one base class, implementations supplied later
a plain non-generic class, one concrete type, fixed forever
Then defend the decision against the other two.
What a good answer contains.
- The component named, with its current callers and its expected ones.
- The decision, and the criterion that decided it — one of: how many genuine callers exist now; is the type a fact about the design, or something the program discovers; does the type have to be known for the operation to be meaningful; what does each option cost the callers.
- The cost of the option you rejected, named specifically. If you rejected the template, that is
usually the duplication you are keeping and the fix that will have to be applied more than once. If
you rejected the virtual interface, that is the dispatch per call and the base class the element
type now has to inherit — which excludes
intand every type somebody else wrote. If you rejected the plain class, that is the flexibility you are giving up and the rewrite when the second case arrives. - A sentence on what would change your mind, stated as an event rather than a feeling: "a second caller appearing", or "the format list becoming configurable".
What would make it weak. "Templates are more modern." The module's own material says otherwise: generality has a cost, and the course's rule is to generalise when a second genuine use appears. Also weak: rejecting the plain class reflexively, when a component with exactly one caller and no plausible second one is correct as a plain class — and easier to read, compile and debug.
Deciding it for yourself. Ask who pays for the flexibility: the author, once, or every caller, forever. Then ask whether anybody will use it.
Evaluating your own answer¶
Write these four lines before reading any suggested approach:
My decision:
The requirement I imposed, and the thing that required it:
The option I rejected, and what it would have cost:
What would change my mind:
Levels: [L6 Build] · [L5 Design] · [L8 Evaluate] — this module's exit level is 6, and Challenge 3 goes beyond it deliberately: the counterweight to a module about generalising is knowing when not to.
Next: Self-assessment — nine questions, including a prediction about compile time versus link time.