Last 30 Days
No notifications
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.
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.
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"};
void*)| Approach | Type-checked? | Optimised? |
| Macros | ❌ | yes (textual) |
void* | ❌ | no (indirect) |
| Templates | ✅ | yes (per type) |
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
Templates are C++'s flagship feature. Almost the entire Standard Library is templates. Once you can read them, the STL stops looking like noise.
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.
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 = stringmax_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)
template <typename A, typename B>
auto plus(A a, B b) { return a + b; } // C++14: trailing 'auto' returnplus(1, 2.5); // returns double
plus(string("hi"), '!'); // returns string
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.
template <typename Key, typename Value>
class Map {
vector<pair<Key, Value>> items;
// ...
};Map<string, int> ages;
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.
template <typename T = int, size_t N = 10>
class Buffer { /* ... */ };Buffer<> b1; // Buffer<int, 10>
Buffer<double> b2; // Buffer<double, 10>
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.
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.
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 */ };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;
}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; }
| Need | Code |
| Generic function | template |
| Generic class | template |
| Constant param | template |
| Default param | template |
| Variadic | template |
| Constrain (C++20) | template |
| Fail-fast assert | static_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.