conceptModern C++~4 min readUpdated 2026-07-02#cpp#templates#generic-programming#concepts#compile-time

Templates and generic programming

Templates are not macros and not runtime polymorphism. They are parameterized families of functions, classes, aliases, variables, and concepts that the compiler turns into concrete specializations when used. Generic programming in C++ means writing code against a set of operations and letting the type system check whether each concrete type satisfies that contract. C++20 concepts make that contract nameable instead of hiding it inside template error fallout.

The reset: a template is source for generating checked concrete code. The abstraction exists at compile time; the instantiated specialization is what the linker sees.

How it really works

A function template describes a family of functions:

template <typename T>
T max_value(T left, T right);

When the compiler sees max_value(1, 2), it can deduce T = int and instantiate an int specialization. When it sees max_value(1.0, 2.0), it can instantiate a double specialization. A class template works the same way for types such as std::vector<int> or Ring<std::string, 3>. Non-type template parameters such as 3 are compile-time values that can shape storage layout and generated code.

Template piece What it means
Type parameter a type chosen by the caller or deduction
Non-type parameter a compile-time value such as a size, pointer, enum, or structural value
Function template a family of functions selected by deduction and overload resolution
Class template a family of types, one per argument list
Specialization custom implementation for a particular pattern
Instantiation compiler creates the concrete declaration/definition that is needed
Concept named compile-time predicate over template arguments

Templates are checked in two phases. Non-dependent code can be checked when the template is defined. Dependent code must wait until arguments are known. That is why errors can point inside template definitions even though the bad call is elsewhere. Concepts improve this by checking the intended requirements before the compiler digs through every expression in the template body.

Definitions usually live in headers because implicit instantiation needs the full definition at the use site. That is different from ordinary non-template C/C++ functions, where a declaration can live in a header and one .c or .cpp file owns the definition. Explicit instantiation can move some template code back into a .cpp, but the default mental model is header-visible implementation and one generated specialization per used argument set, with duplicate link-time copies merged by the toolchain.

Executable artifact: one algorithm, concrete instantiations

The runnable demo lives in examples/modern-cpp/templates-and-generic-programming/. It uses a C++20 concept, a function template over std::span, a class template with a non-type size parameter, and if constexpr for compile-time branching.

cd examples/modern-cpp/templates-and-generic-programming
./run.sh

The concept names the operation the algorithm needs:

template <typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};

template <Addable T>
T total(std::span<const T> values) {
    T result{};
    for (const T &value : values) {
        result = result + value;
    }
    return result;
}

The class template bakes capacity into the type:

template <typename T, std::size_t N>
class Ring {
public:
    void push(T value);
    std::size_t size() const;
    const T &back() const;

private:
    std::array<T, N> items_{};
};

Ring<std::string, 3> and Ring<int, 3> would be different types. No runtime field has to store N; it is part of the type and can shape layout. The script also emits demo.O2.s, which is useful in Compiler Explorer or a local editor: look for the concrete code generated for total<int> and total<double>.

Failure modes & trade-offs

  • Template definitions affect build time. Header-visible implementation means many translation units may parse the same generic code.
  • Code bloat is possible. One template body instantiated for many large types or many non-type values can increase binary size.
  • Diagnostics can still be deep. Concepts improve the front door, but dependent code, overload sets, and nested templates can still produce long errors.
  • The contract is structural. A type satisfies a template by supporting the required operations, not by explicitly declaring "implements interface" unless a concept says so.
  • Specialization can fracture behavior. A clever specialization may violate the assumptions callers learned from the primary template.
  • Generic code can hide expensive operations. T result = result + value is cheap for int, maybe expensive for a big matrix, and maybe wrong for a type with surprising overloads.

In practice

  • Start with ordinary functions. Reach for a template when the same algorithm truly applies to multiple types and the generated code matters.
  • Name requirements with concepts. A good concept turns "template explosion" into a readable compile-time contract.
  • Prefer value-like requirements. Generic code is easiest to reason about when types are regular: constructible, movable, comparable, and unsurprising.
  • Keep templates small at boundaries. Put stable non-template APIs around heavy generic internals when compile times and ABI matter.
  • Inspect instantiations when performance matters. Templates can inline away beautifully or multiply code size. Check generated assembly, symbols, and binary size.

Connects to: Zero-cost abstractions (and where they leak) · The STL: containers, iterators, algorithms · References, const, and overloading · Symbols: definition, reference, and resolution · Object files and what's inside them · The preprocessor: macros, includes, and conditional compilation

Sources

  • cppreference - Templates - template entities, parameters, instantiation, specialization, and header-visibility notes. https://en.cppreference.com/w/cpp/language/templates
  • cppreference - Function templates - deduction, overload interaction, explicit template arguments, and specialization rules. https://en.cppreference.com/w/cpp/language/function_template
  • cppreference - Constraints and concepts - C++20 constraints, requires-expressions, and concept-based overload selection. https://en.cppreference.com/w/cpp/language/constraints
  • C++ working draft - [temp] - standard wording for templates, instantiation, specialization, and deduction. https://eel.is/c++draft/temp
  • C++ working draft - [temp.constr] - standard wording for constraints and concepts. https://eel.is/c++draft/temp.constr
  • C++ Core Guidelines - template guidance around concepts, generic interfaces, and keeping template code understandable. https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines
  • Compiler Explorer - compare template instantiations and inlining under real compilers. https://godbolt.org/