C++ Templates Cheat Sheet
Covers C++ function and class templates, template specialization, variadic templates, and concepts for writing generic, type-safe code.
Function Templates
Write one function that works across multiple types.
template <typename T>T myMax(T a, T b) { return (a > b) ? a : b;}int i = myMax(3, 7); // T deduced as intdouble d = myMax(1.5, 2.5); // T deduced as doublestd::string s = myMax<std::string>("abc", "abd"); // explicit instantiation
Class Templates
Parameterize an entire class by type.
template <typename T>class Stack {public: void push(const T& value) { data.push_back(value); } void pop() { data.pop_back(); } T& top() { return data.back(); } bool empty() const { return data.empty(); }private: std::vector<T> data;};Stack<int> intStack;intStack.push(42);// Non-type template parametertemplate <typename T, size_t N>class FixedArray { T data[N];};FixedArray<int, 10> arr;
Template Specialization
Provide a custom implementation for a specific type.
template <typename T>struct TypeName { static std::string get() { return "unknown"; }};// Full specializationtemplate <>struct TypeName<int> { static std::string get() { return "int"; }};// Partial specialization (only allowed for class/struct templates)template <typename T>struct TypeName<T*> { static std::string get() { return TypeName<T>::get() + "*"; }};
Variadic Templates
Accept an arbitrary number of template arguments.
template <typename T>T sum(T v) { return v; }template <typename T, typename... Args>T sum(T first, Args... rest) { return first + sum(rest...); // recursive parameter pack expansion}sum(1, 2, 3, 4); // 10// C++17 fold expression, no recursion neededtemplate <typename... Args>auto sumFold(Args... args) { return (args + ...);}
Key Concepts
Terminology for reasoning about templates.
- Template Instantiation- The compiler generates concrete code for each distinct set of template arguments used.
- SFINAE- "Substitution Failure Is Not An Error" - invalid substitutions remove an overload from consideration instead of erroring.
- Concepts (C++20)- Named compile-time predicates that constrain template parameters, e.g. template<std::integral T>.
- Type Trait- A compile-time metafunction like std::is_integral<T> or std::enable_if, from <type_traits>.
- Two-Phase Lookup- Template code is checked at definition (non-dependent names) and again at instantiation (dependent names).
C++20 Concepts & Constraints
Constrain template parameters directly with named, composable predicates.
#include <concepts>template <typename T>concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>;};template <Addable T>T add(T a, T b) { return a + b; }// abbreviated function template syntaxauto add2(Addable auto a, Addable auto b) { return a + b; }// requires-clause form, useful when combining multiple constraintstemplate <typename T>requires std::integral<T> || std::floating_point<T>T square(T v) { return v * v; }// constrain a class templatetemplate <std::regular T>class Box { T value; };
Template Template Parameters
Pass a class template itself as a parameter to another template.
template <template <typename, typename> class Container, typename T>class Wrapper { Container<T, std::allocator<T>> data;public: void add(const T& v) { data.push_back(v); } size_t size() const { return data.size(); }};Wrapper<std::vector, int> w;w.add(42);// C++17 class template argument deduction (CTAD)template <typename T>struct Pair { Pair(T a, T b) : first(a), second(b) {} T first, second;};Pair p{1, 2}; // T deduced as int, no <int> needed// user-defined deduction guidetemplate <typename T>Pair(T, T) -> Pair<T>;
if constexpr and Compile-Time Branching
Discard the untaken branch entirely at compile time instead of using tag dispatch or SFINAE.
template <typename T>auto describe(const T& value) { if constexpr (std::is_pointer_v<T>) { return value ? *value : throw std::runtime_error("null"); } else if constexpr (std::is_arithmetic_v<T>) { return value * 2; } else { return value; // e.g. a class type with operator<< }}// combined with fold expressions to print any number of argstemplate <typename... Args>void logAll(Args&&... args) { ((std::cout << args << ' '), ...); std::cout << '\n';}
CRTP (Curiously Recurring Template Pattern)
Achieve static polymorphism with zero virtual-call overhead.
template <typename Derived>class Shape {public: double area() const { // dispatches at compile time, no vtable involved return static_cast<const Derived*>(this)->areaImpl(); }};class Circle : public Shape<Circle> {public: explicit Circle(double r) : r(r) {} double areaImpl() const { return 3.14159 * r * r; }private: double r;};template <typename T>double totalArea(const Shape<T>& s) { return s.area(); }
Metaprogramming Vocabulary
Terms that show up once you move past basic generic functions.
- requires-expression- An unevaluated block, requires(args){ ... }, checking whether expressions/types are valid; the building block behind a concept.
- constexpr if- if constexpr discards the untaken branch at compile time, so it doesn't even need to compile for types where it's invalid.
- Tag Dispatch- Pre-concepts technique: overload on an empty tag type (e.g. std::true_type) selected via std::enable_if or a trait.
- Variable Template- template<typename T> constexpr bool is_foo_v = ...; a template that yields a value rather than a type or function.
- Curiously Recurring Template Pattern (CRTP)- A class derives from a template instantiated with itself, enabling static polymorphism without virtual dispatch.
- Constraint Subsumption- The compiler picks the most-constrained overload among several that satisfy their concepts, resolving ambiguity automatically.
Use C++20 concepts (e.g. template<std::integral T>) instead of std::enable_if SFINAE tricks when available - they give far clearer compiler errors and self-documenting constraints.