Polyglot
Languages C++ templates
C++ § templates

Templates

Templates are C++‘s mechanism for generic programming: types and functions parametrised by other types and by compile-time values. The mechanism dates from C++98 and has grown substantially with each revision; it is sufficiently powerful to be Turing-complete at compile time, and it underlies most of the standard library. Understanding templates is necessary to read non-trivial C++ code: the standard containers, algorithms, smart pointers, type traits, and coroutine machinery are all template-based; the language’s modern idioms (perfect forwarding, type erasure, the CRTP pattern, range adaptors) are template constructions.

This page covers function templates, class templates, parameter kinds, deduction, specialisation, variadic templates, and the practical considerations of using templates idiomatically. Templates as a metaprogramming substrate — SFINAE, recursive instantiation, expression templates — are a substantial topic that this reference treats only at the level needed to understand the interfaces a working programmer encounters.

Function templates

A function template is a parameterised family of functions:

template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int    n = max(3, 5);             // T = int
double d = max(1.5, 2.7);          // T = double
auto   s = max(std::string{"a"}, std::string{"b"});  // T = std::string

The compiler instantiates the template once per distinct type the program uses. Each instantiation is a separate function in the resulting object code; the linker dedups by signature so that the same instantiation in two translation units does not produce a multiple-definition error.

The type parameter T is deduced from the arguments — max(3, 5) deduces T as int because both arguments are int. The deduction process is governed by a set of rules that this page covers below.

Class templates

A class template is a parameterised family of classes:

template <typename T>
class Box {
public:
    Box(T value) : value_(std::move(value)) {}
    T &get() { return value_; }
    const T &get() const { return value_; }

private:
    T value_;
};

Box<int>    bi(42);
Box<std::string> bs("hello");

Until C++17 the type argument was always specified explicitly: Box<int> bi(42). C++17 added class template argument deduction (CTAD), which lets the compiler deduce the argument from the constructor arguments:

Box bi(42);                    // C++17: deduced as Box<int>
Box bs(std::string{"hello"});  // C++17: deduced as Box<std::string>

CTAD operates similarly to function-template deduction; the compiler infers the type from the constructor’s argument types.

Template parameters

Three kinds of template parameter appear:

Type parameters

Introduced with typename (or, equivalently, class):

template <typename T>
T first(const std::vector<T> &v) { return v.front(); }

The two keywords are interchangeable in a template parameter list; typename is more conventional in modern code.

Non-type parameters

A non-type parameter is a value (an integer, a pointer, an enum value, a structural literal type as of C++20):

template <typename T, std::size_t N>
class Array {
public:
    T &operator[](std::size_t i) { return data_[i]; }

private:
    T data_[N];
};

Array<int, 10> a;

The parameter is part of the type: Array<int, 10> and Array<int, 20> are distinct types. std::array (the standard-library counterpart) uses precisely this construction.

Template-template parameters

A parameter that is itself a template:

template <template <typename> class Container, typename T>
void fill(Container<T> &c, T value) {
    for (auto &element : c) element = value;
}

The construction is rare; standard-library types occasionally use it (the parameterisation of allocators). Most generic code uses ordinary type parameters.

Template argument deduction

The compiler deduces template arguments from the call-site arguments by matching the parameter types against the argument types. The principal rules:

  • Top-level const is ignored on by-value parameters. template <typename T> void f(T) called with int and called with const int both deduce T as int.
  • References preserve const. template <typename T> void f(T&) called with const int deduces T as const int (and the parameter type is const int&).
  • Arrays and functions decay to pointers, except when the parameter is a reference. template <typename T> void f(T) called with int[3] deduces T as int*; template <typename T> void f(T&) called with int[3] deduces T as int[3].
  • The forwarding-reference form T&& deduces T as a reference type when the argument is an lvalue.

The deduction process operates on each parameter independently and unifies the deductions; if two parameters require contradictory deductions (f(1, 1.0) for template <typename T> void f(T, T)), the call is ill-formed.

C++17 admits implicit deduction guides for class-template arguments and permits user-written deduction guides:

template <typename T>
class Vec {
public:
    Vec(std::initializer_list<T>);
};

// User-written deduction guide:
template <typename T>
Vec(std::initializer_list<T>) -> Vec<T>;

Vec v{1, 2, 3};       // Vec<int>

Implicit guides are generated for most constructors; user-written guides are needed only for constructors whose parameter types do not directly mention the class’s template parameter.

Template specialisation

A specialisation is a definition of a template for a specific argument or set of arguments. Two kinds:

Full specialisation

A complete definition for a specific argument:

template <typename T>
class Storage {
public:
    void store(const T &v);
};

template <>                          // full specialisation
class Storage<bool> {
public:
    void store(bool v);              // perhaps stores in a bit-packed array
};

The compiler picks the specialisation when the arguments match exactly; otherwise it instantiates the primary template.

Partial specialisation

A definition for a family of arguments. Available for class templates (not function templates):

template <typename T>
class Box {
public:
    void store(const T &v);
};

template <typename T>                // partial specialisation
class Box<T*> {                       // for any pointer type
public:
    void store(T *v);
};

The conventional uses of specialisation are type-trait implementations (most of <type_traits> is a partial-specialisation cascade) and library customisation points where the user adapts a generic mechanism to a specific type.

Function templates do not admit partial specialisation; the conventional substitute is overloading (multiple primary templates with different signatures).

Variadic templates and parameter packs

C++11 admits templates with a variable number of parameters:

template <typename... Args>
void print(const Args &... args) {
    ((std::cout << args << ' '), ...);    // C++17 fold expression
    std::cout << '\n';
}

print(1, "hello", 3.14, std::string{"world"});

The parameter pack Args represents zero or more types; args is a function parameter pack representing the corresponding values. The pack is expanded with ...:

  • args... expands to a comma-separated list: args[0], args[1], ….
  • f(args)... expands to f(args[0]), f(args[1]), ….
  • args + 1... expands to args[0] + 1, args[1] + 1, ….

C++17 added fold expressions — a syntax for folding a pack with a binary operator. The four forms:

(... + args)        // unary left fold:    ((args[0] + args[1]) + args[2]) + …
(args + ...)        // unary right fold:   args[0] + (args[1] + (args[2] + …))
(init + ... + args) // binary left fold
(args + ... + init) // binary right fold

The fold expressions replace what required recursive template instantiation in C++11 and earlier; recursive forms remain in older code.

The principal use of variadic templates is in factory functions and forwarding wrappers:

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args &&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

std::make_unique, std::make_shared, emplace_back, std::tuple constructors, and std::variant constructors all use this form.

Two-phase name lookup

A template body is compiled in two phases:

  1. Template-definition time: names that do not depend on the template parameters are looked up in the surrounding scope.
  2. Instantiation time: names that do depend on the template parameters (dependent names) are looked up at the point of instantiation.

The mechanism interacts with name lookup in subtle ways. Three rules a working programmer should know:

Dependent type names require typename

template <typename T>
void f() {
    T::value_type x;          // ERROR: ambiguous; T::value_type might be a type or a value
    typename T::value_type y; // OK: explicitly disambiguates as a type
}

Inside a template, a name that depends on a template parameter is presumed not to be a type unless declared typename. The keyword is required even when context makes the type-ness apparent.

Dependent template names require template

template <typename T>
void f(T &t) {
    t.method<int>();          // ERROR: ambiguous parsing
    t.template method<int>(); // OK
}

The template keyword tells the parser that method<int> is a template instantiation, not a sequence of comparisons.

Two-phase lookup affects ADL

Dependent names are looked up via argument-dependent lookup (ADL) at instantiation time, in addition to the template-definition-time scope. The rule lets templates call functions defined in the namespace of their template arguments without explicit qualification.

SFINAE

Substitution Failure Is Not An Error: when template argument substitution fails, the compiler removes that overload from consideration rather than diagnosing. The mechanism is the foundation on which type-trait-based overload selection is built.

template <typename T>
auto length(const T &c) -> decltype(c.size()) {
    return c.size();
}

template <typename T>
std::size_t length(const T (&array)[N]) {
    return N;
}

The first overload is viable only for types T whose c.size() is a valid expression; for arrays, substitution fails and the second overload is selected. SFINAE is the C++03/C++11 mechanism for compile-time selection; concepts (C++20) supersede it for most uses.

Variable templates

C++14 introduced variable templates: parameterised constants:

template <typename T>
constexpr bool is_pointer_v = std::is_pointer<T>::value;

static_assert(is_pointer_v<int*>);

The construction is principally used in the standard library to provide short forms of type-trait values (std::is_pointer_v<T> instead of std::is_pointer<T>::value). The user-defined cases are uncommon.

Compilation cost

Templates are instantiated lazily but at compile time. The principal practical consequence is compilation cost: each instantiation re-compiles the template body. A library that uses std::vector heavily, with the same T, instantiates std::vector<T> once per translation unit; the linker dedups, but the compiler has done the work many times.

The conventional mitigations:

  • Move template definitions out of headers and into .cpp files when the set of instantiations is small and known.
  • Use explicit instantiation to materialise specific instantiations in one translation unit and extern templates to suppress instantiation elsewhere.
  • Use C++20 modules, which compile each module once and import it; the textual repetition that plagues the inclusion model is avoided.
// in a header
extern template class std::vector<int>;     // do not instantiate here

// in a single source file
template class std::vector<int>;             // instantiate here

The technique is principally useful for very heavily-templated code; for ordinary use of standard-library templates, the compilation cost is rarely the dominant concern.

Idiomatic use vs metaprogramming

Templates admit two distinct styles:

  • Idiomatic generic code: std::vector<T>, std::sort, std::unique_ptr<T>. The template parameter is a type that the code uses; the template body resembles ordinary code with T standing in for a particular type.
  • Template metaprogramming: code whose template-body computations are themselves the program. Type traits, expression templates, compile-time string manipulation. Substantially harder to read; principally appears in libraries and is rare in application code.

The practical advice: write idiomatic generic code freely; reach for metaprogramming only when no simpler mechanism suffices, and prefer constexpr functions and concepts (C++20) where they apply.

Concepts as the modern replacement

C++20 introduced concepts as the modern mechanism for constraining template parameters. Concepts replace SFINAE for most use cases, produce vastly better error messages, and integrate cleanly with overload resolution. Treated in Concepts.