Templates
|
This section documents C++23 (ISO/IEC 14882:2024), as published by ISO/IEC JTC1/SC22/WG21 (wg21), verified against the freely available working draft N5046 (eel.is/c++draft) and cppreference.com. This content was generated with the assistance of AI and should be verified against the working draft and cppreference.com before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
Function and Class Templates
template <typename T>
T maxOf(T a, T b) {
return a > b ? a : b;
}
template <typename T>
class Box {
public:
explicit Box(T value) : value_(value) {}
T get() const { return value_; }
private:
T value_;
};
typename and class are interchangeable in a template parameter list (template <class T> means exactly the
same thing) — typename is the more common modern convention.
Template Argument Deduction
int m = maxOf(3, 5); // T deduced as int from the arguments -- no need to write maxOf<int>
double d = maxOf(3.0, 5.0); // T deduced as double
// auto bad = maxOf(3, 5.0); // error: T can't be deduced as both int and double -- an explicit
// maxOf<double>(3, 5.0) resolves the ambiguity
CTAD and Deduction Guides
Class Template Argument Deduction (C++17) lets a class template’s constructor arguments determine its template arguments, the same way function templates already worked:
Box b(42); // CTAD: deduces Box<int> from the constructor argument, no "<int>" needed
std::vector v = {1, 2, 3}; // CTAD: deduces std::vector<int>
std::pair p(1, "one"); // CTAD: deduces std::pair<int, const char*>
A deduction guide tells the compiler how to deduce arguments when the constructor alone is ambiguous or insufficient:
template <typename T>
struct Wrapper {
Wrapper(T value) : value_(value) {}
T value_;
};
Wrapper(const char*) -> Wrapper<std::string>; // without this guide, Wrapper("hi") would deduce
// Wrapper<const char*>, not the likely intended Wrapper<std::string>
Non-Type Template Parameters
A template parameter can be a value (an integer, a pointer, an enum, and since C++20 a floating-point value or a literal class type), not just a type:
template <typename T, std::size_t N>
class FixedArray {
public:
T& operator[](std::size_t i) { return data_[i]; }
static constexpr std::size_t size() { return N; }
private:
T data_[N]{};
};
FixedArray<int, 10> arr; // N is a compile-time constant, baked into the type itself
static_assert(arr.size() == 10);
std::array<T, N> is exactly this pattern, in the standard library.
Alias Templates
Covered in Basic Types and Values; they are the
using-based way to give a partially-applied template a shorter name:
template <typename T>
using IntKeyMap = std::map<int, T>;
IntKeyMap<std::string> m; // same as std::map<int, std::string>
Variadic Templates and Fold Expressions
A parameter pack (Args…) accepts any number of template arguments; a fold expression (C++17)
collapses the pack with an operator in one expression, replacing the recursive-unpacking idiom C++11/14 needed:
#include <iostream>
template <typename... Args>
auto sumAll(Args... args) {
return (args + ...); // unary right fold: arg1 + (arg2 + (arg3 + ...))
}
template <typename... Args>
void printAll(const Args&... args) {
((std::cout << args << ' '), ...); // fold over the comma operator -- prints every argument
}
int main() {
std::cout << sumAll(1, 2, 3, 4) << '\n'; // 10
printAll("a", 1, 3.5, 'x'); // a 1 3.5 x
}
Specialization and Partial Specialization
A full specialization provides a completely different implementation for one specific set of template arguments; a partial specialization does so for a pattern of arguments (only available for class templates, not function templates):
template <typename T>
struct TypeName {
static constexpr const char* value = "unknown";
};
template <> // full specialization
struct TypeName<int> {
static constexpr const char* value = "int";
};
template <typename T> // partial specialization: any pointer type
struct TypeName<T*> {
static constexpr const char* value = "pointer";
};
static_assert(std::string_view(TypeName<int>::value) == "int");
static_assert(std::string_view(TypeName<double*>::value) == "pointer");
static_assert(std::string_view(TypeName<double>::value) == "unknown");
Two-Phase Lookup Basics
A template’s body is checked in two phases: phase 1, at definition, resolves names that do not depend on
a template parameter (ordinary lookup applies); phase 2, at instantiation, resolves names that do depend
on a template parameter (found via Argument-Dependent Lookup at the point of instantiation). This is why a
dependent base class’s members need this→ or explicit qualification — phase 1 cannot see them yet:
template <typename T>
struct Base {
void baseMethod() {}
};
template <typename T>
struct Derived : Base<T> {
void callIt() {
// baseMethod(); // error: not found in phase 1 -- Base<T> is a dependent base
this->baseMethod(); // fine: "this->" defers lookup to phase 2, when T is known
}
};
See Also
-
C: Type-Generic Programming —
_Genericselection and macros, the ground templates and overloading supersede.