Notifications

No notifications

/Phase 3

Templates — Generic Programming

Code That Works for Any Type 🧬

A template is a recipe the compiler uses to *generate* a function or class for whatever type you ask for. It's how std::vector, std::map, std::sort all work.

Function Template

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

cout << max_of(3, 7); // T = int → 7 cout << max_of(2.5, 1.1); // T = double → 2.5 cout << max_of<string>("a","b"); // T = string → "b"

The compiler stamps out a fresh function for each type — zero runtime cost.

Class Template

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

Box<int> bi{42}; Box<string> bs{"hello"};

Why Templates Beat Macros (and void*)

ApproachType-checked?Optimised?
Macrosyes (textual)
void*no (indirect)
Templatesyes (per type)

Constraining Templates (C++20 concepts)

#include <concepts>

template <std::integral T> T half(T x) { return x / 2; }

half(10); // ✅ int half(3.14); // ❌ compile error — double isn't integral

On this page

Detailed Theory

Templates are C++'s flagship feature. Almost the entire Standard Library is templates. Once you can read them, the STL stops looking like noise.

Function Templates — Anatomy

template <typename T>          // template parameter list
T max_of(T a, T b) {            // function template
    return (a > b) ? a : b;
}

typename T and class T are interchangeable in this position — typename is the modern preference.

Type Deduction

max_of(3, 5);            // T deduced as int
max_of(3.14, 2.71);      // T deduced as double
max_of(string("a"), string("b"));   // T = string

max_of(3, 5.0); // ❌ ambiguous — T can't be both int and double max_of<int>(3, 5.0); // ✅ explicitly pick T = int (5.0 converted)

Multiple Type Parameters

template <typename A, typename B>
auto plus(A a, B b) { return a + b; }     // C++14: trailing 'auto' return

plus(1, 2.5); // returns double plus(string("hi"), '!'); // returns string

Class Templates

template <typename T>
class Stack {
    vector<T> data;
public:
    void   push(T x)        { data.push_back(std::move(x)); }
    T      pop()             { T x = data.back(); data.pop_back(); return x; }
    bool   empty() const     { return data.empty(); }
    size_t size()  const     { return data.size(); }
};

Stack<int> s_int; Stack<string> s_str;

The same code, compiled twice — once for int, once for string.

Templates of Multiple Parameters

template <typename Key, typename Value>
class Map {
    vector<pair<Key, Value>> items;
    // ...
};

Map<string, int> ages;

Non-Type Template Parameters

You can pass values to templates too — typically integral constants:

template <typename T, size_t N>
class FixedArray {
    T data[N];
public:
    constexpr size_t size() const { return N; }
    T& operator[](size_t i) { return data[i]; }
};

FixedArray<int, 10> a; // 10 ints

This is exactly how std::array works.

Default Template Arguments

template <typename T = int, size_t N = 10>
class Buffer { /* ... */ };

Buffer<> b1; // Buffer<int, 10> Buffer<double> b2; // Buffer<double, 10>

Specialisation — Different Code for Specific Types

template <typename T>
struct TypeName { static const char* value() { return "?"; } };

template <> struct TypeName<int> { static const char* value() { return "int"; } };

template <> struct TypeName<double> { static const char* value() { return "double"; } };

cout << TypeName<int>::value(); // int cout << TypeName<float>::value(); // ?

Use sparingly — usually a sign you should overload functions instead.

Variadic Templates (C++11+) — Any Number of Args

template <typename... Args>
void log(Args... args) {
    (cout << ... << args) << "\n";    // C++17 fold expression
}

log("x=", 42, " y=", 3.14); // x=42 y=3.14

This is how make_unique, std::format, and emplace_back accept arbitrary constructor arguments.

Where Templates Live: Header Files

A template's full definition must be visible at every use site — so put templates in header (.h / .hpp) files, not split header/source like normal functions.

// stack.hpp
template <typename T>
class Stack { /* declarations + bodies all here */ };

Compile Errors — They're Long. Read the FIRST One.

A bad template instantiation can cascade into 50+ lines of error. The first error is usually closest to the real bug. Strategies:

1. Read the first error. 2. Use static_assert to fail early with a clear message. 3. In C++20, use concepts (next) to make requirements explicit.

template <typename T>
T half(T x) {
    static_assert(std::is_arithmetic_v<T>, "half() needs a number");
    return x / 2;
}

C++20 Concepts — Constrained Templates

Concepts let you say "T must be addable" or "T must be sortable" right in the template signature.

#include <concepts>

template <std::integral T> T abs_diff(T a, T b) { return a > b ? a - b : b - a; }

abs_diff(5, 9); // ✅ ints abs_diff(5.0, 9.0); // ❌ compile error — double isn't integral

Common standard concepts: std::integral, std::floating_point, std::totally_ordered, std::ranges::range.

You can also write your own:

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

template <Addable T> T add(T a, T b) { return a + b; }

When NOT to Use Templates

  • One concrete type covers all needs → just write the function
  • Behaviour varies at runtime → use virtual functions, not templates
  • Code size matters a lot → each instantiation adds binary size

Cheat-Sheet

NeedCode
Generic functiontemplate T foo(T a, T b);
Generic classtemplate class Box { T x; };
Constant paramtemplate class FixedArray;
Default paramtemplate ...
Variadictemplate ...
Constrain (C++20)template ...
Fail-fast assertstatic_assert(condition, "msg");

You now read and write generic code — the same skill that powers vector, map, sort, and the entire STL we'll meet next.