Documentation Index
Fetch the complete documentation index at: https://mintlify.com/MicrosoftDocs/cpp-docs/llms.txt
Use this file to discover all available pages before exploring further.
Templates are the foundation of generic programming in C++. Because C++ is strongly typed, every variable and function argument must have a concrete type — but many algorithms and data structures behave identically regardless of the types they operate on. Templates let you write code once and have the compiler generate type-specific versions automatically at compile time. The process of generating a concrete class or function from a template is called template instantiation. This page covers function templates, class templates, type and non-type parameters, default arguments, full and partial specialization, variadic templates, and C++20 concepts.
Function Templates
A function template defines a function in terms of one or more type parameters. When the function is called, the compiler deduces the type arguments from the call arguments and generates a concrete function:
template <typename T>
T minimum(const T& lhs, const T& rhs)
{
return lhs < rhs ? lhs : rhs;
}
int a = 3, b = 7;
int i = minimum<int>(a, b); // explicit type argument: minimum<int>
int j = minimum(a, b); // type deduced: T = int
double x = 1.5, y = 2.5;
double d = minimum(x, y); // T = double
The keyword typename (or equivalently class in this context) introduces the type parameter T. When the compiler instantiates minimum<int>, it generates:
int minimum(const int& lhs, const int& rhs)
{
return lhs < rhs ? lhs : rhs;
}
Type arguments must support every operation the template applies to them. If you call minimum with a type that has no < operator, the compiler generates an error at the point of instantiation. C++20 concepts provide a cleaner way to express these requirements — see the Concepts section below.
Class Templates
Class templates generate class types at compile time. The C++ Standard Library’s containers (std::vector, std::map, etc.) are all class templates:
template <typename T, size_t L>
class MyArray
{
T arr[L];
public:
MyArray() { std::fill(arr, arr + L, T{}); }
T& operator[](size_t i) { return arr[i]; }
const T& operator[](size_t i) const { return arr[i]; }
size_t size() const { return L; }
};
MyArray<int, 10> ints;
MyArray<double, 4> doubles;
ints[0] = 42;
Multiple Type Parameters
template <typename K, typename V>
class Pair {
public:
Pair(K key, V value) : m_key(key), m_value(value) {}
K key() const { return m_key; }
V value() const { return m_value; }
private:
K m_key;
V m_value;
};
Pair<std::string, int> entry("score", 100);
Type Parameters
There is no practical limit on the number of type parameters. Separate multiple parameters with commas. Both typename and class are interchangeable in template parameter lists:
template <typename T, typename U, typename V>
class Foo {};
template <class T, class U, class V>
class Bar {}; // identical to above
// Using std::vector with various types
std::vector<int> vi;
std::vector<std::string> vs;
std::vector<MyClass*> vp;
Non-Type Parameters
Unlike generics in Java or C#, C++ templates support non-type parameters — compile-time constant values such as integers, pointers, or enumerators:
template <typename T, size_t L>
class StaticArray {
T data[L];
public:
constexpr size_t size() const { return L; }
T& operator[](size_t i) { return data[i]; }
};
StaticArray<float, 4> vec4; // 4-element float array on the stack
StaticArray<int, 10> intBuf; // 10-element int array
// C++17: non-type template parameter with auto
template <auto x>
constexpr auto constant = x;
auto v1 = constant<5>; // v1 == 5, decltype(v1) is int
auto v2 = constant<true>; // v2 == true, decltype(v2) is bool
auto v3 = constant<'a'>; // v3 == 'a', decltype(v3) is char
Default Template Arguments
Template parameters can have default arguments, just like function parameters:
// std::vector uses a default allocator argument
template <class T, class Allocator = std::allocator<T>>
class vector { /* ... */ };
// Most code doesn't specify an allocator:
std::vector<int> nums; // uses std::allocator<int>
std::vector<int, MyAllocator<int>> custom; // custom allocator
// Class template with all defaults — use empty angle brackets
template <typename A = int, typename B = double>
class Pair {
A first;
B second;
};
Pair<> p1; // Pair<int, double>
Pair<float> p2; // Pair<float, double>
Pair<float, float> p3; // Pair<float, float>
Template Specialization
Sometimes the generic template code is not correct or optimal for a specific type. Template specialization lets you provide a hand-written version for particular type arguments.
Full (Explicit) Specialization
// Primary template
template <typename T>
class Serializer {
public:
static std::string serialize(const T& val) {
return std::to_string(val);
}
};
// Full specialization for bool
template <>
class Serializer<bool> {
public:
static std::string serialize(bool val) {
return val ? "true" : "false";
}
};
Serializer<int>::serialize(42); // "42"
Serializer<bool>::serialize(true); // "true"
Partial Specialization
Partial specialization applies when only some of the template parameters are fixed:
template <typename K, typename V>
class MyMap { /* generic */ };
// Partial specialization for string keys
template <typename V>
class MyMap<std::string, V> {
// optimized implementation for string keys
};
MyMap<int, double> m1; // uses primary template
MyMap<std::string, double> m2; // uses partial specialization
Only class templates (not function templates) can be partially specialized. For function templates, use overloading or if constexpr (C++17) instead of partial specialization.
Variadic Templates
Variadic templates accept an arbitrary number of type arguments using the ellipsis operator ...:
// A simple type-safe print function using variadic templates
template <typename T>
void print(T value) {
std::cout << value << "\n";
}
template <typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // recursive instantiation
}
print(1, 2.5, "hello", true);
// Output: 1 2.5 hello 1
// Forward all arguments to another function
template <typename... Args>
void log_and_call(void(*fn)(Args...), Args... args) {
std::cout << "Calling function\n";
fn(args...);
}
Variadic templates underpin std::tuple, std::variant, std::make_unique, and many other Standard Library types.
Templates as Template Parameters
A template can itself be a template parameter:
template <typename T, template <typename U, int I> class Arr>
class MyClass2 {
T t;
Arr<T, 10> a;
};
if constexpr for Compile-Time Branching (C++17)
if constexpr selects branches at compile time inside function templates, eliminating the need for many specializations:
template <typename T>
auto describe(T val) {
if constexpr (std::is_integral_v<T>) {
return std::string("integer: ") + std::to_string(val);
} else if constexpr (std::is_floating_point_v<T>) {
return std::string("float: ") + std::to_string(val);
} else {
return std::string("other type");
}
}
describe(42); // "integer: 42"
describe(3.14); // "float: 3.140000"
Concepts (C++20)
Concepts are named compile-time predicates that constrain template parameters. They replace the cryptic error messages that result from constraint violations with clear, readable diagnostics:
#include <concepts>
// Define a concept
template <typename T>
concept Arithmetic = std::is_arithmetic_v<T>;
// Constrain a function template with a concept
template <Arithmetic T>
T add(T a, T b) {
return a + b;
}
add(1, 2); // OK: int satisfies Arithmetic
add(1.5, 2.5); // OK: double satisfies Arithmetic
// add("a", "b"); // Error: const char* does not satisfy Arithmetic
// Concept with requires clause
template <typename T>
concept Comparable = requires(T a, T b) {
{ a < b } -> std::convertible_to<bool>;
};
template <Comparable T>
T my_min(T a, T b) { return a < b ? a : b; }
Prefer standard library concepts from <concepts> (such as std::integral, std::floating_point, std::same_as, std::convertible_to) before writing your own. They are well-tested and compose naturally.
Practical Example: Type-Safe Stack
template <typename T, size_t Capacity = 64>
class Stack {
public:
void push(const T& item) {
if (m_size >= Capacity)
throw std::overflow_error("Stack full");
m_data[m_size++] = item;
}
T pop() {
if (m_size == 0)
throw std::underflow_error("Stack empty");
return m_data[--m_size];
}
const T& top() const { return m_data[m_size - 1]; }
bool empty() const { return m_size == 0; }
size_t size() const { return m_size; }
private:
T m_data[Capacity];
size_t m_size = 0;
};
Stack<int> istack;
Stack<double, 32> dstack;
istack.push(1);
istack.push(2);
std::cout << istack.pop(); // 2
See Also