Templates & Generics
max function that works for int,
double, or your own types — this is how the STL containers achieve their
type-safety. No C knowledge is assumed: everything builds from what you learned so far.
Function templates
A function template is written once, with a placeholder type. The compiler generates a specific version for every type it is called with.
template <typename T> // T is the placeholder type
T maximum(T a, T b) {
return (a > b) ? a : b;
}
int main() {
std::cout << maximum(3, 7) << "\n"; // T = int
std::cout << maximum(2.5, 1.5) << "\n"; // T = double
return 0;
}
The compiler instantiates two separate functions here — one per T. That is why
templates achieve speed: each uses the exact type's operators, with no runtime overhead.
Type deduction
Normally you do not name T; the compiler deduces it from the arguments. When the
arguments disagree, say so explicitly:
maximum(1, 2.5); // error: T cannot be both int and double
maximum<double>(1, 2.5); // OK: T is forced to double
A template only compiles for types that support the operations it uses. maximum
requires > and the ternary — every type you pass must provide them.
Class templates
A class template parameterizes a whole class — this is exactly how std::vector<int>
and std::vector<double> can share one implementation. You write the template once
and name the type at the point of use:
template <typename T>
class Box {
public:
explicit Box(T value) : value(value) {}
T get() const { return value; }
private:
T value;
};
Box<int> intBox(42); // T = int
Box<double> dblBox(3.14); // T = double
std::cout << intBox.get() << " " << dblBox.get();
The angle brackets at the call site are mandatory for class templates — there are no arguments for the compiler to deduce the type from.
Concepts
A template accepts any type that satisfies its operations — that is flexible but loose. A concept (C++20) names the requirements explicitly, so misuse fails with a clear message instead of a wall of template errors:
#include <concepts>
template <std::integral T> // T must be an integer type
T square(T x) {
return x * x;
}
square(4); // OK: int is integral
// square(2.5); // error: double is not integral — clear message
Use concepts when the requirement matters (numbers, containers, comparable types). For simple
internal helpers, a plain typename template is fine.
Template pitfalls
Templates move work to compile time, so their failure modes differ from ordinary functions:
- Verbose errors — an invalid instantiation produces a long cascade. Read the first "required from here" line; concepts fix most cases.
- Code bloat — every distinct type instantiation produces machine code. Do not create hundreds of specializations of a heavy template.
- Header-only reality — templates are usually defined in headers because the compiler must see the body to instantiate. The build systems lesson explains header organization.
- Explicit instantiation —
template Box<int>;compiles the version now, which can shorten build times in big projects.
Practice
- Write a
minimumfunction template and test it withint,double, andstd::string. - Create a
Pair<F, S>class template holding two values of possibly different types. - Add a
static_assertinside a template that requiressizeof(T) >= 4and observe the error forchar. - Replace the assert with a C++20
std::integralconcept and compare the error quality.