Bank 10 — Templates¶
Eleven exercises on writing a shape of code once and letting the compiler fill in the type. Module 12. Levels L1–L6.
Solutions are in Solutions and answers; nudges are in Hints. Attempt first.
Exercises¶
E106 — One shape, many types L1 concept¶
Task. In one sentence each: (a) what a function template is; (b) when it is turned
into real code; (c) what "instantiation" means. Then answer: how many copies of a
max<T> function exist in the compiled program if you call it with int, double and
std::string?
E107 — What type did I get? L2 predict¶
#include <iostream>
#include <string>
template <typename T>
T larger(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << larger(3, 9) << '\n';
std::cout << larger(2.5, 1.5) << '\n';
std::cout << larger<std::string>("apple", "pear") << '\n';
// std::cout << larger(3, 9.5) << '\n'; // line M
}
Task. Write the three output lines. Then explain what would go wrong on line M if
you uncommented it, and why — be precise about how the compiler deduced T.
E108 — Write the template L2 complete¶
#include <iostream>
#include <vector>
// TODO: a function template `sum` that adds every element of a container
// and returns the total. It must work for std::vector<int> and std::vector<double>.
int main() {
std::vector<int> a{ 1, 2, 3, 4 };
std::vector<double> b{ 1.5, 2.5 };
std::cout << sum(a) << '\n';
std::cout << sum(b) << '\n';
}
Task. Write sum so the program prints 10 then 4. What type does the template
deduce in each call, and what should the return type be? (Keep it simple: assume a
non-empty container with arithmetic elements.)
E109 — Two kinds of record L2 trace scenario¶
A registry holds records of two different kinds, using one template.
#include <iostream>
#include <string>
#include <vector>
template <typename T>
class Registry {
public:
void add(const T& value) { items_.push_back(value); }
std::size_t size() const { return items_.size(); }
const T& at(std::size_t i) const { return items_[i]; }
private:
std::vector<T> items_;
};
int main() {
Registry<int> numbers;
numbers.add(10);
numbers.add(20);
Registry<std::string> names;
names.add("Ada");
names.add("Linus");
names.add("Grace");
std::cout << numbers.size() << ' ' << names.size() << '\n';
std::cout << numbers.at(1) << ' ' << names.at(2) << '\n';
}
Task. Write the two output lines. Then say how many distinct Registry types the
compiler generated, and why Registry<void> would be an error.
E110 — Specialise it L3 modify¶
#include <iostream>
#include <string>
template <typename T>
std::string describe(const T& value) {
return "value: " + std::to_string(value);
}
int main() {
std::cout << describe(42) << '\n';
// std::cout << describe(std::string("hello")) << '\n'; // will not compile
}
Task. describe(std::string) does not compile because std::to_string has no
overload for std::string. Add a full specialisation of describe for
std::string that returns "text: hello", and uncomment the second line. Why is a
specialisation the right tool here rather than an if constexpr inside the general
version?
E111 — It builds, then it does not link L3 debug¶
A team split a template across two files.
Task. This compiles each file cleanly but fails to link. Explain why, in terms of
when the compiler instantiates twice<int>. Then fix it, and say where a template's
definition must live. Reproduce the real linker message with your toolchain.
E112 — A size that is part of the type L2 predict¶
#include <iostream>
template <typename T, int N>
class Ring {
public:
void put(int index, T value) { data_[index] = value; }
T get(int index) const { return data_[index]; }
int capacity() const { return N; }
private:
T data_[N];
};
int main() {
Ring<int, 4> r;
r.put(0, 10);
r.put(3, 40);
std::cout << r.capacity() << '\n';
std::cout << r.get(0) + r.get(3) << '\n';
}
Task. Write the two output lines. N is a non-type template parameter — say what
that means, and how Ring<int, 4> and Ring<int, 8> differ as types.
E113 — Why templates live in headers L4 analyze¶
Task. Explain, without jargon you cannot define, why the definition of a function
template usually has to be visible at the point where it is used, not merely declared.
Your answer must mention what the compiler produces when it sees a concrete call. Then
give one technique that lets a template's definition live in a .cpp file anyway.
E114 — The error is about the type L4 debug¶
#include <iostream>
#include <string>
#include <vector>
template <typename T>
T smallest(const std::vector<T>& values) {
T best = values[0];
for (const T& v : values) {
if (v < best) best = v;
}
return best;
}
struct Reading {
std::string sensor;
double value;
};
int main() {
std::vector<Reading> rs{ {"t1", 3.0}, {"t2", 1.0} };
std::cout << smallest(rs).value << '\n';
}
Task. This produces a long error. Reproduce it, read it bottom-up, and state in
one sentence what is missing from Reading. Then supply the minimum addition that makes
it compile, and say why the template was not the thing that was wrong.
E115 — Design a generic store L5 design template¶
A course needs one store type to hold marks (double) keyed by student id
(std::string), and later another to hold counts (int) keyed by the same id. Lookups
must be fast, and adding an item must not invalidate references to existing ones if it
can be avoided.
Task. Design the store. Give:
- the template's name and its parameters (how many type parameters, and why);
- the operations it offers, with their signatures;
- the underlying container you chose and why — justify it against one alternative;
- one constraint you will document in a comment so callers know what the type must support (e.g. it must be comparable or hashable);
- one thing you deliberately did not template, and why.
How you will be judged. On the container justification and on the documented requirement, which is the contract of a template.
E116 — Delete the copies L6 refactor template scenario¶
Three near-identical classes each hold a differently-typed dynamic array, each with its
own add, size, get and destructor — the same code with int, double and
std::string substituted.
Task. Write a refactoring plan that replaces them with one template. It must:
- name the template and its type parameter;
- state which of the three classes' operations become one template member;
- name one operation that cannot be made generic, and how you handle it;
- say how you would check that all three original behaviours are preserved;
- name the cost: what is harder to reason about (or to compile) after the change.
Success. One template replaces three copies, and you have stated what you traded away.
Previous topic: RAII and smart pointers · Next topic: Exceptions · Hints: hints.md · Solutions: solutions.md