Exercises — Templates — function templates, class templates
Every answer that produces a number is machine-verified.
Level 1 — Recognition (can you read the syntax?)
L1·Q1 — Spot the template head
Recall Solution
Answer: B.
The keyword is template, followed by an angle-bracket list of parameters, and each type parameter must be introduced by typename (or the equivalent class). So template<typename T> (or template<class T>).
- A is wrong:
Tis used before it is declared — you cannot write<T>, you must say "T is a type":<typename T>. - C and D just scramble the keywords.
L1·Q2 — Count the instantiations
Recall Solution
Answer: 3. Instantiation is keyed by the concrete type, not by how many times you call it.
twice(3)andtwice(4)both deduceT = int→ oneintversion, shared.twice(2.5)deducesT = double→ a second version.twice<long>(9)forcesT = long→ a third version. Same type = same generated function reused. So the two int-calls collapse to one function, and adding the double and long versions gives three distinct functions total.
L1·Q3 — Which need explicit <...>?
Recall Solution
- (i) compiles. You gave the type explicitly:
Box<int>. - (ii) does NOT compile in C++14. A class template cannot deduce
Tfrom its constructor before C++17. You must writeBox<int> b(5);. (In C++17+ Class Template Argument Deduction makesBox b(5)legal, but the question pins C++14.)
Level 2 — Application (make it work)
L2·Q4 — Fix the deduction conflict
Recall Solution
The failure: 3 makes deduction say T = int, 2.5 makes it say T = double. One T, two demands → conflict.
Fix A — force the type explicitly:
auto r = maximum<double>(3, 2.5); // 3 converts to 3.0, T = doubleFix B — two type parameters:
template<typename T1, typename T2>
auto maximum(T1 a, T2 b){ return (a > b) ? a : b; }
auto r = maximum(3, 2.5);Value of r: compare 3 and 2.5; 3 is greater than 2.5, so the result is 3 (as a double, i.e. 3.0).
L2·Q5 — Non-type parameter size
Recall Solution
- (a)
Nis baked in as5, soa.size()returns 5. - (b) The only member is
double data[5], i.e. 5 doubles at 8 bytes each: 5 times 8 is 40 bytes. Sosizeof(a) == 40.Nbeing a compile-time value (a non-type parameter, defined above) is exactly why the array lives on the stack with a fixed, known size — see Compile-time vs Runtime.
L2·Q6 — Write a generic swap
Recall Solution
template<typename T>
void swapValues(T& a, T& b){ // references, so the caller's variables change
T tmp = a; // stash a
a = b; // a becomes b
b = tmp; // b becomes old a
}Why T&? Without references we'd swap copies and the caller would see nothing. The & binds to the real variables.
Trace: start x=1, y=9. tmp=1; x=9; y=1. Final: x == 9, y == 1.
Level 3 — Analysis (why does it behave this way?)
L3·Q7 — Linker error diagnosis
Recall Solution
Why it fails: A template is not code — it's a recipe. Code is generated only at a point of use. The point of use is in main.cpp. But main.cpp only sees the declaration from math.h; the body lives in math.cpp, a separate translation unit the main.cpp compiler never reads. With no body, the compiler cannot instantiate square<int>, so it emits a call to a function that is never defined → linker error.
Fix: Put the full definition in the header (math.h). Then every file that includes it sees the whole body and can instantiate.
Value: once fixed, square(4) returns 4 times 4, which is 16.
L3·Q8 — Which overload / specialization wins?
Recall Solution
Rule of thumb: a non-template exact match beats a template; among templates the most specialized wins.
- (i)
pick(5)→ theintfull specialization of the template exists and matches exactly →"int-special". - (ii)
pick(2.0)→ there is an ordinary (non-template)pick(double). A non-template function is preferred over any template when both match equally well →"plain-double". - (iii)
pick('a')→ no special version forchar; the primary template instantiates withT = char→"generic". See Template Specialization and Function Overloading for the full ranking rules.
Level 4 — Synthesis (build something new)
L4·Q9 — A generic Pair with a swap
Recall Solution
#include <type_traits>
template<typename A, typename B>
class Pair {
public:
A first;
B second;
Pair(A a, B b) : first(a), second(b) {}
void flip() {
// hard compile-time guard: refuse unless A and B are the SAME type
static_assert(std::is_same<A, B>::value,
"flip() requires both members to have the same type");
A tmp = first;
first = second;
second = tmp;
}
};Why static_assert(std::is_same<A,B>::value, ...)? Without it, flip() would silently compile for any two convertible types (say int and short), which is not what we want. The static_assert makes the compiler reject Pair<int,short>::flip() with a clear message, so only genuinely same-type pairs work.
Trace: Pair<int,int> p{3,8} → first=3, second=8. flip(): tmp=3; first=8; second=3. Result: p.first == 8, p.second == 3.
L4·Q10 — Specialize for a pointer-printing Printer
Recall Solution
template<typename T>
struct Printer<T*> { // partial specialization: any pointer
static const char* kind(){ return "pointer"; }
};The pattern Printer<T*> matches any pointer type and is more specialized than the primary template, so the compiler prefers it whenever the argument is a pointer.
Printer<int>::kind()→ no*, primary template →"value".Printer<int*>::kind()→ matchesT*withT = int→"pointer".
Level 5 — Mastery (put it all together)
L5·Q11 — Generic fixed-size stack
Recall Solution
template<typename T, int N>
class Stack {
T data[N]; // fixed storage, size known at compile time
int top = 0; // number of elements currently held
public:
void push(T v){ data[top++] = v; }
T pop(){ return data[--top]; }
int count() const { return top; }
};Trace top: start 0. push, push, push → 3. pop → 2. push, push → 4.
So s.count() == 4. (Capacity N=4 is a non-type parameter baked in at compile time; the buffer lives on the stack.)
L5·Q12 — Compare macro vs template safety
Recall Solution
- (a) Text substitution gives:
int j = ((i++) > (3) ? (i++) : (3));i++appears twice. It is evaluated once in the condition, and — becausei++returns5which is> 3— the true branch runsi++again. Soiis incremented twice. This "double evaluation" is the classic macro trap. - (b) Start
i = 5. Conditioni++: uses 5,ibecomes 6. Since 5 is greater than 3, the true branchi++runs: uses 6,ibecomes 7. Finali == 7(andj == 6). - (c) A template is a real function, not text substitution. Its argument
ais evaluated exactly once to initialize the parameter, and then only that stored parameter (a copy) is compared and returned — the caller's expressioni++is never re-run. SomaxT(i++, 3)incrementsia single time. This single-evaluation guarantee, together with full type-checking, is the core reason templates beat macros. See Macros vs Templates.
Recall One-line self-check
Templates deduce type from arguments (functions) but need it spelled out for classes pre-C++17 ::: True — function argument deduction vs no constructor deduction. A macro double-evaluates its arguments; a template evaluates each argument once ::: True — templates are real functions. Non-type template parameters must be compile-time constants ::: True.