Skip to content

Templates

Templates allow you to write generic code that works with any data type. They are the foundation of the STL.

Function Templates

Instead of writing multiple functions for int, double, etc., write one template.

1
2
3
4
5
6
7
template <typename T>
T max_value(T a, T b) {
    return (a > b) ? a : b;
}

int i = max_value(10, 20);       // T is int
double d = max_value(3.14, 2.5); // T is double

Template Argument Deduction

The compiler usually deduces T automatically. You can also specify it explicitly:

max_value<double>(10, 2.5); // Forces T=double, 10 istack.push(10);

Class Templates

Useful for container classes.

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

Box<int> intBox(123);
Box<std::string> strBox("Hello");

Template Specialization

You can provide a specific implementation for a particular type.

Full Specialization

Use this when you want completely different behavior for a specific type.

// Generic
template <typename T>
void print(T val) {
    std::cout << val << "\n";
}

// Specialization for bool
template <>
void print<bool>(bool val) {
    std::cout << (val ? "True" : "False") << "\n";
}

Partial Specialization

You can specialize a template for a subset of types (e.g., all pointer types), while keeping some template parameters generic. Note that function templates cannot be partially specialized (use overloading instead); this mostly applies to class templates.

template <typename T>
class SmartPointer {
    T ptr;
public:
    SmartPointer(T p) : ptr(p) {}
};

// Partial specialization for pointers being passed as T*
template <typename T>
class SmartPointer<T*> {
    T* ptr;
public:
    SmartPointer(T* p) : ptr(p) {
        std::cout << "Specialized for pointers!\n";
    }
};

int x = 10;
SmartPointer<int> s1(x);   // Generic
SmartPointer<int*> s2(&x); // Specialized

Non-Type Template Parameters

Templates can take values, not just types.

1
2
3
4
5
6
7
8
template <typename T, int Size>
class Array {
    T data[Size];
public:
    int get_size() const { return Size; }
};

Array<int, 10> my_array; // Size is known at compile-time

Variadic Templates (C++11)

Templates that accept an arbitrary number of arguments. In C++11, this is typically handled via recursion:

  1. Base case: Handle the last element (or empty state).
  2. Recursive step: Process one argument and call the function again with the rest.
// Base case: recursion stops here
template<typename T>
T sum(T t) {
    return t;
}

// Recursive step
template<typename T, typename... Args>
T sum(T t, Args... args) {
    return t + sum(args...); // Arg expansion
}

int total = sum(1, 2, 3, 4, 5); // 15

Fold Expressions (C++17)

C++17 introduced fold expressions, which dramatically simplify variadic templates by removing the need for recursion.

1
2
3
4
5
template<typename... Args>
auto sum(Args... args) {
    return (... + args); // Unary left fold
}
// sum(1, 2, 3) expands to ((1 + 2) + 3)

Concepts (C++20)

Concepts allow you to constrain template parameters, making error messages readable and interfaces clearer. Instead of just typename T, you specify what kind of T is allowed.

#include <concepts>

// T must be an integral type (int, long, etc.)
template <std::integral T>
T add(T a, T b) {
    return a + b;
}

// add(10, 20);   // OK
// add(1.5, 2.5); // Error: double is not integral

Deducing This (C++23)

C++23 introduces "Explicit Object Parameter", often called "Deducing This". It allows the member function to deduce the type of this (the object it's called on), which helps with CRTP-like patterns and recursive lambdas.

1
2
3
4
5
6
7
8
struct Widget {
    // 'self' captures the object instance (like 'self' in Python/Rust)
    template <typename Self>
    void process(this Self&& self) {
        // self can be const Widget&, Widget&&, etc.
        std::cout << "Processing widget\n";
    }
};

Common Pitfalls: Templates and Header Files

A very common mistake is defining template functions in a .cpp file.

Rule: Template definitions must usually be in the header file (.h / .hpp).

Why?

Templates are not code; they are "blueprints". The compiler generates the actual code (instantiation) only when the template is used. If the definition is in a .cpp file, compiling main.cpp won't be able to see the blueprint to generate the code, leading to "undefined reference" linker errors.

Incorrect: math.h: template<typename T> T add(T a, T b); math.cpp: template<typename T> T add(T a, T b) { return a + b; }

Correct: math.h: Put the implementation here!

1
2
3
4
template<typename T>
T add(T a, T b) {
    return a + b;
}