Lesson 16 / 25

Function and Class Templates

Write generic functions and classes with templates and understand instantiation.

Code that works for many types

Templates let you write a function or class once for many types, with full static type checking. A function template such as template <typename T> T max_of(T a, T b) is a blueprint; when you call it with int or double, the compiler instantiates a specialised version for that type, so there is no run-time cost. The compiler usually deduces template arguments from the call; you can also specify them explicitly (max_of<double>(3, 4.5)). Class templates (template <typename T> class Stack) work the same way, and since C++17 class template argument deduction lets you write std::vector v{1, 2, 3};. Templates can also take non-type parameters, such as std::array<int, 5>. Because the compiler needs the full template definition to instantiate it, templates are normally defined in headers. The STL is built entirely on templates. Classic downsides are long compile times and famously long error messages, which concepts (next topic) greatly improve.

One template, many instantiations

The compiler stamps out a concrete version of a template for each type it is used with.

A stencil shape on the left with arrows to three solid copies of the shape in different colours on the right.
Figure 6.1 — Template instantiation for different types.

A function template and a class template

The compiler generates code for each type actually used.

#include <stdexcept>
#include <string>
#include <vector>

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

template <typename T>
class Stack {
public:
    void push(T value) { items_.push_back(std::move(value)); }
    T pop() {
        if (items_.empty()) throw std::out_of_range("pop from empty stack");
        T top = std::move(items_.back());
        items_.pop_back();
        return top;
    }
    [[nodiscard]] bool empty() const { return items_.empty(); }
private:
    std::vector<T> items_;
};

int main() {
    int a = max_of(3, 7);                               // instantiates max_of<int>
    std::string s = max_of(std::string{"pear"}, std::string{"apple"});   // max_of<std::string>
    Stack<std::string> undo;
    undo.push("typed hello");
    auto last = undo.pop();
}

Templates live in headers

Putting a template's definition in a .cpp file and only its declaration in a header leads to "undefined reference" link errors, because other files cannot instantiate it. Define templates in headers (or use explicit instantiation for a known set of types).

Quick check: When does the compiler generate code for a function template?

  • When the template is declared
  • At run time
  • When it is used with specific types (instantiation)
  • Only in debug builds
Answer

When it is used with specific types (instantiation) — Templates are instantiated for each set of template arguments actually used.