Templates, Deeper

Course 3 · Ch 2
Templates, Deeper
cpp2-1's own deferred promise, kept — and a discipline Rust simply had from day one

cpp2-1 deferred two things: the deeply confusing pre-C++20 template error, and full template depth. This chapter delivers both.

Template Specialization

A different implementation of a template for one specific type.

template<typename T> class Box { /* generic version */ }; template<> class Box<bool> { /* a completely different implementation, just for bool */ };

A real, well-known example: std::vector<bool> has its own famous specialization, bit-packing elements instead of storing full bytes — a genuinely different implementation from every other std::vector<T>.

Partial Specialization

Specializing for a category of types rather than one exact type — a real, useful middle ground.

template<typename T> class Box<T*> { /* a different implementation for ANY pointer type */ };

Variadic Templates

template<typename... Args> accepts an arbitrary number of template arguments.

template<typename... Args> void print_all(Args... args) { ((std::cout << args << " "), ...); // C++17 fold expression } print_all(1, "two", 3.0);

This is genuinely what powers std::make_unique — used unexplained since cpp2-4 — and similar "forward any number of constructor arguments" functions throughout the STL.

The Confusing Error, Resolved — C++20 Concepts

cpp2-1's own deferred promise: concepts let a template constrain what operations T must support, directly in the template's own declaration.

template<typename T> concept Addable = requires(T a, T b) { a + b; }; template<Addable T> T sum(T a, T b) { return a + b; }

This moves the error from deep inside the template's own instantiated body to a clear, readable message at the call site itself, naming exactly which requirement wasn't met — a genuine, real quality-of-life improvement.

Contrasted With Rust's Own Trait Bounds

Rust's fn sum<T: Add>(a: T, b: T) -> T has always worked this way, since Rust's very first stable release. Trait bounds constraining a generic parameter aren't a recent addition to Rust the way concepts are to C++ (added in C++20, decades after templates themselves) — another point where Rust simply had, by default and from day one, a discipline C++ has only recently, and optionally, caught up to.

ConceptC++ Pre-C++20C++20 ConceptsRust Trait Bounds
Constraint expressed where?nowhere — implicit, discovered on instantiationin the template declaration itselfin the function signature itself, since day one
Error on unmet requirementdeep, confusing, nested inside the instantiationclear, at the call site, naming the requirementclear, at the call site, always
When introducedtemplates since ~1998concepts added in C++20present from Rust's first stable release
Reach for concepts in new code
In C++20 and later, prefer concept-constrained templates over raw, unconstrained ones whenever possible — the improved error messages alone are a genuinely significant quality-of-life win.
Variadic template code can become genuinely hard to read
Overused, variadic templates and fold expressions can produce code that's flexible but genuinely difficult to read and debug — a real, practical readability cost worth weighing against the flexibility gained.

Coding Challenges

Challenge 1

Write a class template Box with a generic implementation, then write a full specialization Box with a different implementation (e.g. storing a single bit-flag style member instead of a plain bool). Demonstrate both are used correctly for their respective types.

📄 View solution
Challenge 2

Write a variadic template function sum_all(Args... args) that adds together an arbitrary number of arguments using a fold expression, and call it with 2, 3, and 5 arguments, printing each result.

📄 View solution
Challenge 3

Explain precisely what C++20 concepts change about WHERE a template's requirements are checked and reported, and why Rust never needed an equivalent feature added later in its own history.

📄 View solution

Chapter 2 Quick Reference

  • Full specialization — a completely different implementation for one specific type
  • Partial specialization — a different implementation for a category of types (e.g. all pointers)
  • Variadic templatestypename... Args, an arbitrary number of arguments; powers make_unique and similar STL functions
  • C++20 concepts — constraints declared up front, clear errors at the call site instead of deep inside instantiation
  • Rust's trait bounds have worked this way since its first stable release — no equivalent retrofit was ever needed
  • Next chapter: the Rule of Five and copy/move semantics, in full mechanical depth