C++, End to End
Templates and Generic Classes
An earlier chapter covered function templates. Class templates apply the same idea to whole classes: write the logic once, parameterized by type, and let the compiler generate a concrete version for each type you actually use.
Class templates
Box<int> and Box<std::string> are two entirely different, independently compiled classes as far as the compiler is concerned, generated from the same template. This is why templates are described as compile-time polymorphism: there's no runtime dispatch overhead like virtual functions have, because there's no ambiguity left to resolve by the time the program runs, every use site already knows exactly which concrete type it's dealing with.
Template specialization
Sometimes the generic implementation is wrong (or just suboptimal) for a specific type. Full specialization lets you provide a completely separate implementation for one exact type:
Partial specialization does the same thing but for a subset of a multi-parameter template (for example, specializing a Pair<T, U> template just for when U is a pointer type), rather than one exact concrete type.
Variadic templates and parameter packs
A variadic template accepts any number of template arguments, using a parameter pack (Args...). This is how things like std::make_unique and std::tuple are implemented under the hood.
That ((std::cout << args << " "), ...) is a fold expression (C++17): it expands the parameter pack, applying the comma operator across every argument, which is a compact way to "do this for every argument in the pack" without writing recursive template unpacking by hand, which is how this was done before C++17 and was considerably uglier.
Concepts: constraints that actually explain themselves
Before C++20, constraining a template to only accept certain kinds of types relied on SFINAE (substitution failure is not an error), a genuinely clever but genuinely unreadable technique involving std::enable_if and template metaprogramming tricks. If you got it wrong, the compiler error was often a wall of template instantiation backtraces with no clear indication of what you actually did wrong.
Concepts (C++20) replace most of that with a named, readable constraint you can attach directly to a template parameter:
If you're writing a template that only makes sense for certain kinds of types, use a concept to say so directly, rather than letting a misuse fail deep inside the implementation with a confusing error. Concepts are a genuinely large usability improvement, and by now the reasonable default whenever you'd have reached for SFINAE in older code.
Try it yourself: write a constrained template›
Write a template function clamp(value, low, high) constrained to only accept types satisfying std::totally_ordered (a standard concept meaning the type supports all six comparison operators), then try calling it with a type that doesn't satisfy it and read the resulting error.