C++ Template Fundamentals

Last update: 2021-05-03 19:04:55 +0800 +0800
Available translations: 日本語 / 中文(简体) / 中文(繁體)

A template is a declaration family parameterized by types, constant values, templates, and packs. A specialization is formed under C++ name lookup, type checking, overload resolution, and constraint rules; templates are not textual substitution.

Template Parameters #

Type parameters accept types, constant template parameters accept structural constant values, and template template parameters accept a compatible template. Defaults provide omitted arguments. A parameter pack represents zero or more parameters or arguments.

cpp
template <class T, std::size_t N = 16>
struct Buffer;

template <auto Value>
struct Constant;

template <template <class...> class Container, class T>
using one_element_container = Container<T>;

template <class... Ts>
struct TypeList {};

A class or alias template can have type, constant, template, and pack parameters. Function templates add deduction: arguments occurring in deduced parameter positions are inferred from call arguments, while a non-deduced parameter must be supplied or obtained elsewhere.

cpp
template <class T>
T maximum(T left, T right);

auto a = maximum(1, 2);       // T is int
auto b = maximum<double>(1, 2.5);

Explicit template arguments precede ordinary call arguments. Deduction does not use a function return type because a call’s result may be discarded and because overload resolution needs candidate types before a result is known.

Template Categories and CTAD #

Function, class, variable, alias, and member templates have distinct instantiation rules.

cpp
#include <type_traits>

template <class T> T identity(T value) { return value; } // function
template <class T> struct Box { T value; };               // class
template <class T> inline constexpr bool is_box = false;  // variable
template <class T> using remove_cvref_t =                 // alias
    std::remove_cvref_t<T>;

struct Store {
    template <class T> void put(T&& value);               // member
};

Class template argument deduction (CTAD) chooses class template arguments from constructors, aggregate deduction candidates, or user-defined deduction guides. It applies when a class template name is used without explicit arguments; it does not apply to function templates.

cpp
template <class T>
struct Pair { T first; T second; };

template <class T, class U>
Pair(T, U) -> Pair<std::common_type_t<T, U>>;

Pair values{1, 2}; // Pair<int> through the deduction guide

Write a deduction guide when constructor parameter types do not express the intended stored type. Do not rely on CTAD at an API boundary if spelling the type makes ownership, units, or narrowing policy clearer.

Instantiation and Visibility #

Implicit instantiation occurs when a specialization is needed, subject to point-of-instantiation rules. The relevant template definition must be reachable at that point, which is why function and class-template definitions conventionally reside in headers.

An explicit instantiation definition emits a specialization; an extern template declaration suppresses implicit instantiation in a translation unit that can use an instantiation supplied elsewhere.

cpp
// widget.hpp
template <class T>
class Widget {
public:
    void draw();
};

extern template class Widget<int>;

// widget.cc, after the definition is visible
template <class T>
void Widget<T>::draw() {}

template class Widget<int>;

Explicit instantiation is a build-time control, not a substitute for making every required specialization valid. Keep declarations visible before their uses, and keep template definitions visible wherever the compiler must instantiate them.

Comments

Read or comment on GitHub (sign-in required to comment)