Templates Quick Reference
C++ templates cheat sheet — function/class/variable templates, specialization, SFINAE, concepts, fold expressions, CRTP, and template metaprogramming patterns.
Function templates
cpp
// Basic function template
template<typename T>
T max_val(T a, T b) { return a > b ? a : b; }
// Multiple type parameters
template<typename T, typename U>
auto add(T a, U b) -> decltype(a + b) { return a + b; }
// C++14: deduced return type
template<typename T, typename U>
auto multiply(T a, U b) { return a * b; }
// Abbreviated (C++20): auto parameter = hidden template
auto square(auto x) { return x * x; }
// Call: explicit vs deduced
max_val(3, 4); // T deduced as int
max_val<double>(3, 4); // T explicitly doubleExplicit instantiation and explicit specialization
cpp
// --- Explicit instantiation declaration (in header) ---
// Suppresses implicit instantiation in this TU
extern template int max_val<int>(int, int);
// --- Explicit instantiation definition (in exactly one .cpp) ---
template int max_val<int>(int, int);
// --- Full (explicit) specialization ---
template<>
const char* max_val<const char*>(const char* a, const char* b) {
return std::strcmp(a, b) > 0 ? a : b;
}Class templates
cpp
template<typename T, int Capacity = 16>
class Stack {
public:
void push(const T& val);
T pop();
bool empty() const { return size_ == 0; }
private:
T data_[Capacity];
int size_ = 0;
};
// Member definitions outside class body
template<typename T, int Capacity>
void Stack<T, Capacity>::push(const T& val) {
data_[size_++] = val;
}
template<typename T, int Capacity>
T Stack<T, Capacity>::pop() {
return data_[--size_];
}
// Instantiation
Stack<int> s1; // Capacity = 16 (default)
Stack<double, 8> s2;Non-type template parameters
cpp
// Integer NTTP
template<int N>
struct Array { int data[N]; };
Array<4> a; // N == 4 at compile time
// C++17: auto NTTP — deduces the type too
template<auto N>
struct Constant {
static constexpr auto value = N;
};
Constant<42> c1; // N is int 42
Constant<'A'> c2; // N is char 'A'
Constant<3.14> c3; // N is double 3.14 (C++20: floating-point NTTPs allowed)
// Pointer / reference NTTPs
template<const char* Str>
struct Tag {};Template template parameters
cpp
// A template that takes a container template as a parameter
template<typename T, template<typename, typename> class Container = std::vector>
class Queue {
Container<T, std::allocator<T>> storage_;
public:
void enqueue(const T& v) { storage_.push_back(v); }
T dequeue() { T v = storage_.front(); storage_.erase(storage_.begin()); return v; }
};
Queue<int> q1; // backed by std::vector
Queue<int, std::deque> q2;Variadic templates and parameter packs
cpp
// Pack expansion with sizeof...
template<typename... Args>
void log(Args&&... args) {
std::println("arg count: {}", sizeof...(args));
// Expand each arg:
(std::print("{} ", args), ...); // fold expression (C++17)
std::println();
}
// Recursive (pre-C++17) variadic
template<typename Head, typename... Tail>
void print_all(Head h, Tail... t) {
std::print("{} ", h);
if constexpr (sizeof...(t) > 0) print_all(t...); // C++17 if constexpr
}
// Perfect forwarding of a pack
template<typename... Args>
auto make_vec(Args&&... args) {
return std::vector{std::forward<Args>(args)...};
}Fold expressions (C++17)
cpp
// (pack op ...) — unary right fold
// (... op pack) — unary left fold
// (pack op ... op init) — binary right fold
// (init op ... op pack) — binary left fold
template<typename... Ts> auto sum(Ts... v) { return (v + ...); }
template<typename... Ts> auto all_true(Ts... v){ return (v && ...); }
template<typename... Ts> auto product(Ts... v) { return (1 * ... * v); } // init = 1
// Print all with comma operator fold
template<typename... Ts>
void print_all_fold(Ts&&... args) {
((std::print("{} ", args)), ...);
std::println();
}
// Push multiple items
template<typename Container, typename... Ts>
void push_all(Container& c, Ts&&... vals) {
(c.push_back(std::forward<Ts>(vals)), ...);
}SFINAE with std::enable_if_t
cpp
#include <type_traits>
// Enabled only for integral types
template<typename T,
std::enable_if_t<std::is_integral_v<T>, int> = 0>
T twice(T x) { return x * 2; }
// Enabled only for floating-point types
template<typename T,
std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
T twice(T x) { return x + x; }
// Via return type
template<typename T>
std::enable_if_t<std::is_pointer_v<T>, T>
advance_ptr(T p, std::ptrdiff_t n) { return p + n; }void_t detection idiom (C++17)
cpp
#include <type_traits>
// Primary: T has no 'value_type' member
template<typename T, typename = void>
struct has_value_type : std::false_type {};
// Specialization: T has 'value_type' member
template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};
static_assert( has_value_type<std::vector<int>>::value); // true
static_assert(!has_value_type<int>::value); // true
// Check for a callable serialize(T)
template<typename T, typename = void>
struct is_serializable : std::false_type {};
template<typename T>
struct is_serializable<T, std::void_t<decltype(serialize(std::declval<T>()))>>
: std::true_type {};Concepts (C++20)
cpp
#include <concepts>
// --- requires clause (on any template) ---
template<typename T>
requires std::integral<T>
T halve(T x) { return x / 2; }
// --- requires expression — ad-hoc constraint ---
template<typename T>
requires requires(T a, T b) {
{ a + b } -> std::convertible_to<T>;
{ a.size() } -> std::same_as<std::size_t>;
}
T add_same(T a, T b) { return a + b; }
// --- Abbreviated function template (C++20) ---
void print_integral(std::integral auto x) { std::println("{}", x); }
auto clamp(std::totally_ordered auto lo,
std::totally_ordered auto hi,
std::totally_ordered auto val) {
return std::clamp(val, lo, hi);
}
// --- Named concept definition ---
template<typename T>
concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>; };
template<Addable T>
T sum3(T a, T b, T c) { return a + b + c; }
// --- Concept + default ---
template<std::regular T = int>
class Box { T value; };Partial specialization (class templates only)
cpp
// Primary template
template<typename T, typename U>
struct Pair { T first; U second; };
// Partial: both types the same
template<typename T>
struct Pair<T, T> {
T first, second;
bool equal() const { return first == second; }
};
// Partial: second type is a pointer
template<typename T, typename U>
struct Pair<T, U*> {
T first;
U* second; // pointer semantics
};
// Partial: specialize on a template template parameter
template<typename T>
struct Pair<std::vector<T>, std::vector<T>> {
std::vector<T> first, second;
};Full (explicit) specialization
cpp
// Primary
template<typename T>
struct Formatter { std::string format(const T& v); };
// Full specialization for bool
template<>
struct Formatter<bool> {
std::string format(bool v) { return v ? "true" : "false"; }
};
// Full specialization for a function template
template<typename T>
void swap_vals(T& a, T& b) { T tmp = a; a = b; b = tmp; }
template<>
void swap_vals<std::string>(std::string& a, std::string& b) { a.swap(b); }CRTP — Curiously Recurring Template Pattern
cpp
// Base provides behaviour derived from Derived's interface
template<typename Derived>
class Comparable {
public:
bool operator<=(const Derived& o) const {
const auto& self = static_cast<const Derived&>(*this);
return !(o < self);
}
bool operator>=(const Derived& o) const {
const auto& self = static_cast<const Derived&>(*this);
return !(self < o);
}
bool operator>(const Derived& o) const {
const auto& self = static_cast<const Derived&>(*this);
return o < self;
}
bool operator==(const Derived& o) const {
const auto& self = static_cast<const Derived&>(*this);
return !(self < o) && !(o < self);
}
};
struct Point : Comparable<Point> {
int x, y;
bool operator<(const Point& o) const {
return x == o.x ? y < o.y : x < o.x;
}
};
// Static polymorphism (zero-overhead, no vtable)
template<typename Derived>
class Logger {
public:
void log(std::string_view msg) {
static_cast<Derived*>(this)->write_impl(msg); // static dispatch
}
};
struct FileLogger : Logger<FileLogger> {
void write_impl(std::string_view msg) { /* write to file */ }
};if constexpr in templates
cpp
#include <type_traits>
template<typename T>
std::string describe(const T& v) {
if constexpr (std::is_same_v<T, bool>) {
return v ? "boolean true" : "boolean false";
} else if constexpr (std::is_integral_v<T>) {
return "integer: " + std::to_string(v);
} else if constexpr (std::is_floating_point_v<T>) {
return "float: " + std::to_string(v);
} else if constexpr (requires { v.size(); }) {
return "sized container, size=" + std::to_string(v.size());
} else {
return "(unknown)";
}
}
// Discarded branches are not instantiated — avoids hard errorsdecltype, std::declval, trailing return types
cpp
// decltype(expr) — type of an expression without evaluating it
int n = 0;
decltype(n) x = 5; // int
decltype(n+1) y = 5; // int (n+1 not evaluated)
decltype(auto) z = (n); // int& (preserves ref-ness)
// std::declval<T>() — create a T in unevaluated context (no T ctor needed)
template<typename T, typename U>
using AddResult = decltype(std::declval<T>() + std::declval<U>());
// Trailing return type
template<typename T, typename U>
auto add_two(T a, U b) -> decltype(a + b) { return a + b; }
// C++14: just auto, return type deduced
template<typename T, typename U>
auto add_two14(T a, U b) { return a + b; }
// decltype(auto) — deduce including ref qualifiers
template<typename Container>
decltype(auto) first_element(Container& c) { return c[0]; } // returns T& for vectorBranching strategy comparison
| Technique | C++ version | Compile-time | Readable | Overload set |
|---|---|---|---|---|
SFINAE + enable_if_t | C++11 | yes | low | yes |
| Tag dispatch | C++11 | yes | medium | yes |
if constexpr | C++17 | yes | high | no |
Concepts requires | C++20 | yes | highest | yes |
When to use each:
if constexpr— single function body, different branches per type; simple and readable.- SFINAE — still needed in C++11/14 or to constrain overload sets (concepts not available).
- Tag dispatch — explicit dispatch on
std::true_type/std::false_type; useful when the tag logic is shared. - Concepts — C++20 preferred; expressive constraints, best error messages, proper overload resolution.
Common type traits quick reference
cpp
// Identity / relationships
std::is_same_v<T, U> // T and U are the same type (after neither decay)
std::is_base_of_v<Base, Derived>// Derived publicly inherits Base
std::is_convertible_v<From, To> // From is implicitly convertible to To
std::is_constructible_v<T, Args...> // T can be constructed from Args
// Category checks
std::is_integral_v<T> // bool, char, int, long, ...
std::is_floating_point_v<T> // float, double, long double
std::is_arithmetic_v<T> // integral || floating_point
std::is_pointer_v<T>
std::is_reference_v<T> // lvalue OR rvalue reference
std::is_lvalue_reference_v<T>
std::is_rvalue_reference_v<T>
std::is_array_v<T>
std::is_class_v<T>
std::is_enum_v<T>
std::is_void_v<T>
std::is_function_v<T>
// Modifiers / transformations
std::remove_cv_t<T> // remove const and volatile
std::remove_reference_t<T> // remove & or &&
std::remove_cvref_t<T> // remove cv AND ref (C++20)
std::decay_t<T> // array->ptr, func->ptr, remove cvref
std::add_const_t<T>
std::add_lvalue_reference_t<T>
std::add_rvalue_reference_t<T>
std::add_pointer_t<T> // T -> T*
// Compound
std::conditional_t<Cond, T, U> // Cond ? T : U
std::enable_if_t<Cond, T> // T if Cond, else substitution failure
std::void_t<Ts...> // void if all Ts well-formed (detection)
std::type_identity_t<T> // just T (prevents deduction)
// Numeric meta
std::common_type_t<Ts...> // common arithmetic type of Ts
std::make_signed_t<T>
std::make_unsigned_t<T>
std::underlying_type_t<EnumT> // underlying integer type of an enumPitfalls
cpp
// 1. Template definition must be in headers (not .cpp), unless explicitly instantiated
// 2. Dependent names need 'typename' and 'template' keywords
template<typename T>
void foo() {
typename T::value_type x; // 'typename' required
T::template rebind<int>::other y; // 'template' required
}
// 3. Two-phase lookup: non-dependent names looked up at definition time,
// dependent names at instantiation time.
// 4. SFINAE only on substitution failure — hard errors (e.g., static_assert)
// inside a template body still cause a compiler error.
// 5. Concepts give better error messages than SFINAE — prefer them in C++20.
// 6. Partial specialization is for class/variable templates only; you cannot
// partially specialize a function template (use overloading instead).