Skip to content

Module 12 — Lab 12: one repository, several kinds of record

Levels: [L3 Modify → L6 Build] · Extends: Lab 11 · Time: 3–4 hours


The scenario

By Lab 11 your library system had three containers that were doing the same job: a catalogue of items, a register of members, and a set of loans. Each had its own add, its own find, its own size, and its own copy of the same loop. Two bugs found in one of them had already been fixed twice — and a fourth record kind is coming, because the campus wants to track equipment loans in the same system.

You are going to replace all three with one component, Repository<T>, and you are going to write down what it requires of the type it stores. The requirements are the deliverable as much as the code is: a generic component whose requirements exist only in your head has not been generalised, it has been tested twice.


What you start from

Your Lab 11 program, and these three containers, which are real and correct — and all the same shape:

class Catalogue {
public:
    void add(const Item& item);
    const Item* find(int id) const;
    std::size_t size() const;
    void forEach(const std::function<void(const Item&)>& visit) const;
private:
    std::vector<Item> items_;
};

class MemberRegister { /* identical, with Member */ };
class LoanLedger     { /* identical, with Loan, plus a count of overdue loans */ };

Read them before you replace them. The differences are the interesting part: what the element type appears in, and the one extra operation LoanLedger has.


Requirements

  1. One Repository<T> replacing at least two of the three containers. The third may stay if you can justify it — and justifying it is part of the work.

  2. The operations: add, find by a key or a predicate, remove, size, and iteration. The element type should appear only where it must (interacts with Module 4: the repository's own invariant must still be protected, and a container reference handed out is still a broken invariant).

  3. At least one operation that depends on the element type's requirements — ordering, equality, or a key accessor — with those requirements documented in the header, next to the declaration. Say what T must provide and why each item is required.

  4. A static_assert rejecting at least one unsuitable type with a readable message. On this toolchain, the message appears as one line:

error: static assertion failed: Repository<T>::sortedById() requires T to have an int member named id

If the requirement is harder to express than a trait can check, say so in the header and explain what the failure looks like instead.

  1. The definition visible where it is instantiated — no link errors. If you use explicit instantiation instead of a header-only definition, say why, and note what it costs.

  2. Two instantiations in use in the program, for two genuinely different record types, with a note on what the compiler generated for each — including the sizeof of each instantiation and one sentence on why they differ, or why they do not.

  3. A recorded error reduction. Use the repository with a type that does not satisfy its requirements, capture the whole diagnostic, then reduce it to at most five lines, quote the line that names the cause, and write one sentence saying what the compiler was telling you about your type. This is a required deliverable, not a debugging footnote. (It is the lab's one deliberate failure, and the record is what makes the next storm manageable.)

  4. It must build with no warnings using the course's compile command:

g++ -std=c++17 -Wall -Wextra -Werror main.cpp -o library

What to deliver

  • The generic repository in use for two record types, with the three containers replaced.
  • The documented requirement list, in the header, next to the template it constrains.
  • The static_assert, and its message quoted from a build where it fired.
  • The recorded template error, reduced to five lines, with the causal line quoted.
  • A note on the two instantiations: what was generated, and what it cost.
  • A sentence on the container you did not replace, and why.

Self-assessment checklist

It builds and runs

  • [ ] Compiles with g++ -std=c++17 -Wall -Wextra -Werror with no warnings
  • [ ] Both instantiations run, and produce the same results the three hand-written containers did
  • [ ] No link errors, and I can explain why definition visibility matters

It is correct

  • [ ] Boundary cases work: an empty repository, one element, duplicates, a missing key
  • [ ] The repository's invariant is protected from outside mutation
  • [ ] Removal and iteration behave correctly when they happen in the same operation
  • [ ] A type that does not meet the requirements fails with a message I can read

It is designed

  • [ ] I can state the repository's responsibility in one sentence — if it needs two, I have two components
  • [ ] The element type appears only where it must in the interface
  • [ ] The requirements are written down, and each one is required by something in the body

It is verified

  • [ ] I instantiated it with two unrelated types, not two similar ones
  • [ ] I forced the requirement failure on purpose, captured it, and reduced it
  • [ ] I checked the removal and iteration paths, not only add and find
  • [ ] I said what generalising cost: compile time, code size, error complexity

It is explained

  • [ ] I can say why a template was the right tool here, and name the case where a virtual interface would have been right instead
  • [ ] I can explain what the compiler generated for each instantiation

Common ways this lab goes wrong

Symptom Cause
undefined reference to 'Repository<Book>::add(Book const&)', or a linker message naming no symbol at all The definition is hidden in a .cpp. Move it to the header, or instantiate explicitly
The repository compiles for the first record type and fails for the second A requirement that the first type satisfied incidentally. Write it down, then constrain or generalise
The same add/find loop still exists three times The third container was copied instead of replaced, or the operation was kept on the class rather than moved into the repository
where() returns std::vector<T> and the caller now has copies A query returning values copies; return references, indices, or iterate with a callback
Hundreds of lines of error, none naming a file of mine The reduction step was skipped. Read the last line, find your own filename in the required from chain, reproduce in five lines
error: no matching function for call to 'Repository<Book>::add(Member&)' The two instantiations were mixed up: a Repository<Book> was given a Member. Each instantiation is a different type with no conversion
The lab takes twice the estimate You are generalising all three containers and inventing a new record type. Two containers and two types is the requirement

Reference solution

No solution file is published for this lab. It is one repository type that must serve several record kinds, and the decisions are the work: what the type requires of a record, where a requirement is discovered only by instantiating the template a second time, and what the compiler's error tells you when it is not satisfied. A published file would remove exactly the step that teaches this module.

The model code is in examples/module-12/, one program per idea, each already walked through on the concepts page where it is used:

Program What it models
01-function-templates Function templates: one definition, many types
02-class-templates Class templates: one class, many element types
03-non-type-parameters A template parameter that is a value rather than a type
04-specialisation-and-traits Specialisation, type traits, and if constexpr
05-generic-repository A generic repository — the class this lab's catalogue needs

Compare after your own version builds and runs — the reduced error message is the part worth comparing.


Next: Challenge problems — three open problems, including arguing that a component should not be a template. · Previous: Lab 11 · Then: Module 13 — Exception Handling — Lab 13 makes failure part of the design, and assumes the resources in your system are owned by types.