Class Templates

Class templates are classes parameterised by types: std::vector<T>, std::map<K, V> and std::optional<T> are all class templates. Writing your own lets you build reusable containers and utilities that work for any type, with full type safety.

A simple class template

C++
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>

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;
    }
    bool empty() const { return items_.empty(); }
    std::size_t size() const { return items_.size(); }
private:
    std::vector<T> items_;
};

int main() {
    Stack<int> numbers;
    numbers.push(1);
    numbers.push(2);
    std::cout << numbers.pop() << ' ' << numbers.size() << '\n';

    Stack<std::string> undo;
    undo.push("type 'hello'");
    undo.push("delete word");
    while (!undo.empty()) std::cout << "undo: " << undo.pop() << '\n';
}
Output
2 1
undo: delete word
undo: type 'hello'

Stack<int> and Stack<std::string> are two distinct classes, generated from one template.

Class template argument deduction

Since C++17, the compiler can often deduce template arguments from a constructor, so you can leave them out:

C++
#include <iostream>
#include <string>
#include <utility>
#include <vector>

template <typename A, typename B>
struct Pair {
    A first;
    B second;
};

int main() {
    std::vector v{1, 2, 3};                  // std::vector<int>
    std::pair p{"age", 42};                  // std::pair<const char*, int>
    Pair q{std::string{"pi"}, 3.14};         // Pair<std::string, double>
    std::cout << v.size() << ' ' << p.second << ' ' << q.first << '=' << q.second << '\n';
}
Output
3 42 pi=3.14

Multiple and default parameters

C++
#include <array>
#include <iostream>

template <typename T, std::size_t Capacity = 4>
class RingBuffer {
public:
    void push(const T& value) {
        data_[next_] = value;
        next_ = (next_ + 1) % Capacity;
        if (count_ < Capacity) ++count_;
    }
    std::size_t size() const { return count_; }
    T oldest() const { return data_[(next_ + Capacity - count_) % Capacity]; }
private:
    std::array<T, Capacity> data_{};
    std::size_t next_ = 0, count_ = 0;
};

int main() {
    RingBuffer<int> last4;                   // Capacity defaults to 4
    for (int i = 1; i <= 6; ++i) last4.push(i * 10);
    std::cout << last4.size() << " kept, oldest " << last4.oldest() << '\n';

    RingBuffer<double, 2> last2;
    last2.push(1.5);
    last2.push(2.5);
    last2.push(3.5);
    std::cout << last2.oldest() << '\n';
}
Output
4 kept, oldest 30
2.5

Member functions outside the class

When defining members outside the class body, repeat the template header:

C++
#include <iostream>

template <typename T>
class Box {
public:
    explicit Box(T value);
    const T& get() const;
private:
    T value_;
};

template <typename T>
Box<T>::Box(T value) : value_(std::move(value)) {}

template <typename T>
const T& Box<T>::get() const { return value_; }

int main() {
    Box b{3.5};
    std::cout << b.get() << '\n';
}
Output
3.5

As with function templates, all of this belongs in a header file.

Specialisation

Sometimes one type needs different behaviour. A specialisation replaces the template for a particular argument:

C++
#include <iostream>
#include <string>

template <typename T>
struct TypeName {
    static std::string get() { return "unknown"; }
};

template <> struct TypeName<int>    { static std::string get() { return "int"; } };
template <> struct TypeName<double> { static std::string get() { return "double"; } };

int main() {
    std::cout << TypeName<int>::get() << ' ' << TypeName<double>::get() << ' '
              << TypeName<char>::get() << '\n';
}
Output
int double unknown

The standard library uses specialisation for things like std::hash; you can specialise it so your own types work as unordered_map keys.

Try it yourself

Write a Statistics<T> class template that collects values and reports the count, minimum and maximum.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
4 temps, 9.5 to 21
first Ana, last Zoe