Ch15: Generic Programming: Concepts, Templates, and Type Erasure
1. Why This Chapter Exists
Chapter 4 answered “what is a
vector” with ::fast_io::vector<int>, and never
said where the <int> came from. Ch6.6
showed overload resolution picking between add(int, int) and
add(double, double), and stopped where overloading stops helping: the
moment the bodies are identical and only the type differs.
That moment is where generic programming begins. Its idea is not “fill in a type”; it is write the algorithm once, state what it needs, and let the compiler check that statement. Four tools do the work, and they are the four halves of this chapter:
- traits — compile-time facts about a type (Ch15.1);
- concepts — named requirements the compiler checks before it instantiates (Ch15.3);
- templates — how to define one, and everything about instantiating it: values as parameters, deduction, packs, expansion (Ch15.2, Ch15.4–Ch15.8);
- type erasure — what to do when the choice really must be made at run time (Ch15.10).
This chapter has one enemy: bloat. Every distinct template argument produces a full copy of everything the template touches, and a program that instantiates the same logic a thousand times is a slow-to-compile, huge binary. So the chapter teaches templates and, in the same breath, the discipline of keeping them small: concepts to sharpen them, detemplatization to shrink them, and type erasure to hide them behind one type.
2. The One-Sentence Summary of Each Idea
| Idea | One sentence | Subchapter |
|---|---|---|
| type trait | A compile-time fact about a type, as a bool or as another
type. |
Ch15.1 |
| template definition | A declaration with a parameter list; the compiler writes one copy per argument. | Ch15.2 |
| concept | A named list of requirements the compiler checks before it instantiates; used to classify a type, not to build a dispatch. | Ch15.3 |
| non-type parameter | A template argument that is a value, not a type. | Ch15.4 |
| deduction | The compiler writing the <...> for you, from the
arguments. |
Ch15.5 |
| parameter pack | Zero or more template arguments, consumed by a fold or by a
template for. |
Ch15.6 |
template for |
A loop the compiler unrolls at compile time, one iteration per element. | Ch15.7 |
if constexpr |
An if whose unselected branch is thrown away, not compiled. |
Ch15.8 |
| detemplatization | Move everything that does not touch the template argument out of the template. | Ch15.9 |
| type erasure | One non-template type that can hold values of many types, with no inheritance. | Ch15.10 |
print/scan support |
A tag variable and a concept: how fast_io's generic printing finds your type. | Ch15.11 |
3. The Running Example
One tiny type follows us through the chapter: a colour with three 8-bit channels and a name.
struct rgb
{
::std::uint_least8_t r{}, g{}, b{};
[[nodiscard]] ::std::string_view name() const noexcept
{
return "rgb";
}
};
It is small enough to print in the middle of a page, it has a member function to
constrain, fields to compare, and an obvious textual form
(7,128,255) to scan back in. The last page of the chapter teaches
fast_io to print and scan exactly this type.
4. Conventions Used in This Chapter
-
Every function is
noexceptorthrows; nothing is left without an error contract (Ch6.16). Anoexcepttemplate is a promise about every instantiation, sonoexceptis used only where no instantiation can fail. When the contract depends on the argument, it is written conditionally —throws(!noexcept(...))(Ch15.8). -
Failures are herbceptions:
throw throws ...andcatch throws (::std::error e)(Ch3.16). Function-pointer and template-parameter types carry their error contract too, sovoid (*)(void *) noexceptandvoid (*)(void *) throwsare different types. -
Integers are
::std::int_least64_t,::std::uint_least8_t,::std::size_t; floating point is::std::float64_t. -
Every complete program on these pages was compiled with
-std=c++26 -fherbceptions -lherbceptionsand run; the output shown is what it actually printed. Instantiation counts were read fromnm -Con the linked binary. -
Comparators are named function objects (
::std::ranges::greater), never lambdas, unless a lambda is the point being made (Ch15.9).
5. What You Should Already Know
- Overloading (Ch6.6) — templates are overloading where the compiler writes the copies.
constexprandconsteval(Chapter 7) — a template argument usually has to be a constant expression.- Lambdas (Ch6.13) — every lambda is a distinct type, which is why Ch15.9 exists.
- The Rule of Five (Chapter 14) — the traits in Ch15.1 are the ones that chapter used implicitly, and Ch15.10 needs copy, move and destructor again.
::std::vector's interface (Ch4.3) — Ch15.9 takes fast_io'svectorapart.
6. Contents
- Ch15.1: Type Traits: What the Compiler Knows About a Type
- Ch15.2: Defining Templates: Functions, Classes, Members
- Ch15.3: Concepts, and Why Not to Dispatch With Them
- Ch15.4: Non-Type Template Parameters: Enums, Structural Types, Variable Templates
- Ch15.5: Template Argument Deduction and CTAD
- Ch15.6: Variadic Templates, Fold Expressions, Perfect Forwarding
- Ch15.7:
template for: Expansion Statements and Template Indexing - Ch15.8:
if constexprand Conditional Error Contracts - Ch15.9: Template Bloat, Lambdas, and How fast_io Detemplatizes
vector - Ch15.10: Type Erasure:
shape, Without Inheritance - Ch15.11: How fast_io Finds Your Print and Scan Code