Docs
README
Advanced Templates in C++
Table of Contents
- •Introduction
- •Variadic Templates
- •Template Template Parameters
- •SFINAE
- •Type Traits
- •Concepts (C++20)
- •Fold Expressions
- •Constexpr and Templates
- •Template Metaprogramming
- •Advanced Patterns
- •Best Practices
Introduction
Advanced templates enable powerful compile-time programming:
| Technique | Purpose | C++ Version |
|---|---|---|
| Variadic Templates | Variable number of args | C++11 |
| SFINAE | Conditional overloads | C++11 |
| Type Traits | Query/modify types | C++11 |
| if constexpr | Compile-time branching | C++17 |
| Fold Expressions | Pack operations | C++17 |
| Concepts | Named constraints | C++20 |
Variadic Templates
Templates that accept variable number of arguments:
Basic Syntax
// ═══════════════════════════════════════════════════════════
// Parameter Pack - typename... Args represents 0 or more types
// ═══════════════════════════════════════════════════════════
template<typename... Args>
void func(Args... args); // Function parameter pack
// Example: Print any number of arguments
// Base case (empty pack)
void print() {
cout << endl;
}
// Recursive case
template<typename T, typename... Args>
void print(T first, Args... rest) {
cout << first << " ";
print(rest...); // Recursively process remaining
}
// Usage
print(1, 2.5, "hello", 'x');
// Output: 1 2.5 hello x
Parameter Pack Operations
// ═══════════════════════════════════════════════════════════
// sizeof... - Count elements in pack
// ═══════════════════════════════════════════════════════════
template<typename... Args>
void info() {
cout << "Number of args: " << sizeof...(Args) << endl;
}
info<int, double, char>(); // Output: Number of args: 3
// ═══════════════════════════════════════════════════════════
// Pack expansion patterns
// ═══════════════════════════════════════════════════════════
template<typename... Args>
void examples(Args... args) {
// Direct expansion
int arr1[] = {args...}; // {1, 2, 3}
// Expression applied to each
int arr2[] = {(args * 2)...}; // {2, 4, 6}
// Function call on each
int arr3[] = {process(args)...};
// Pattern with comma operator (for side effects)
(cout << ... << args); // Print all (C++17)
}
Perfect Forwarding with Variadic Templates
// ═══════════════════════════════════════════════════════════
// Forward arguments to another function preserving value category
// ═══════════════════════════════════════════════════════════
template<typename... Args>
auto make_unique_wrapper(Args&&... args) {
return make_unique<MyClass>(forward<Args>(args)...);
}
// In-place construction pattern (like emplace)
template<typename T, typename... Args>
T* construct_at(void* ptr, Args&&... args) {
return new (ptr) T(forward<Args>(args)...);
}
Variadic Class Templates
// ═══════════════════════════════════════════════════════════
// Tuple-like class
// ═══════════════════════════════════════════════════════════
// Base case: empty tuple
template<typename... Args>
struct Tuple {};
// Recursive case: head + tail
template<typename Head, typename... Tail>
struct Tuple<Head, Tail...> : Tuple<Tail...> {
Head value;
Tuple(Head h, Tail... t) : Tuple<Tail...>(t...), value(h) {}
};
// Usage
Tuple<int, double, string> t(1, 2.5, "hello");
// ═══════════════════════════════════════════════════════════
// Type list
// ═══════════════════════════════════════════════════════════
template<typename... Types>
struct TypeList {
static constexpr size_t size = sizeof...(Types);
};
using MyTypes = TypeList<int, double, string>;
cout << MyTypes::size; // 3
Template Template Parameters
Templates that accept other templates as parameters:
// ═══════════════════════════════════════════════════════════
// Basic syntax
// ═══════════════════════════════════════════════════════════
template<typename T,
template<typename> class Container> // Template template param
class Wrapper {
Container<T> data;
public:
void add(const T& value) { data.push_back(value); }
size_t size() const { return data.size(); }
};
// Usage
Wrapper<int, vector> w;
w.add(42);
// ═══════════════════════════════════════════════════════════
// With variadic template template parameters
// ═══════════════════════════════════════════════════════════
template<typename T,
template<typename, typename...> class Container>
class FlexibleWrapper {
Container<T> data;
public:
void add(const T& value) { data.push_back(value); }
};
// Works with containers that have extra template params
FlexibleWrapper<int, vector> v; // vector has allocator param
FlexibleWrapper<int, list> l;
// ═══════════════════════════════════════════════════════════
// Practical example: Container adapter
// ═══════════════════════════════════════════════════════════
template<template<typename, typename...> class Container>
class ContainerStats {
public:
template<typename T>
static double average(const Container<T>& c) {
if (c.empty()) return 0;
return accumulate(c.begin(), c.end(), T{})
/ static_cast<double>(c.size());
}
};
vector<int> v = {1, 2, 3, 4, 5};
cout << ContainerStats<vector>::average(v); // 3.0
SFINAE
Substitution Failure Is Not An Error - enables conditional template instantiation.
Basic SFINAE with enable_if
#include <type_traits>
// ═══════════════════════════════════════════════════════════
// Enable function only for integral types
// ═══════════════════════════════════════════════════════════
template<typename T>
typename enable_if<is_integral<T>::value, T>::type
process(T value) {
cout << "Integral: " << value << endl;
return value * 2;
}
// Enable function only for floating point types
template<typename T>
typename enable_if<is_floating_point<T>::value, T>::type
process(T value) {
cout << "Floating: " << value << endl;
return value * 0.5;
}
process(10); // Uses integral version, returns 20
process(10.0); // Uses floating point version, returns 5.0
// process("hi"); // Error: no matching function
// ═══════════════════════════════════════════════════════════
// C++14 helper: enable_if_t
// ═══════════════════════════════════════════════════════════
template<typename T>
enable_if_t<is_integral_v<T>, T>
processV2(T value) {
return value * 2;
}
SFINAE with decltype
// ═══════════════════════════════════════════════════════════
// Check if type has a specific method
// ═══════════════════════════════════════════════════════════
// Helper to detect .size() method
template<typename T>
auto print_size(const T& container)
-> decltype(container.size(), void()) // SFINAE here
{
cout << "Size: " << container.size() << endl;
}
// Fallback for types without .size()
void print_size(...) { // Catch-all
cout << "Size unknown" << endl;
}
vector<int> v = {1, 2, 3};
print_size(v); // Size: 3
print_size(42); // Size unknown
// ═══════════════════════════════════════════════════════════
// void_t trick (C++17)
// ═══════════════════════════════════════════════════════════
template<typename, typename = void>
struct has_size : false_type {};
template<typename T>
struct has_size<T, void_t<decltype(declval<T>().size())>>
: true_type {};
static_assert(has_size<vector<int>>::value); // true
static_assert(!has_size<int>::value); // true (int has no size())
C++17 if constexpr
// ═══════════════════════════════════════════════════════════
// Compile-time branching (simpler than SFINAE!)
// ═══════════════════════════════════════════════════════════
template<typename T>
auto process(T value) {
if constexpr (is_integral_v<T>) {
return value * 2; // Only compiled for integral
} else if constexpr (is_floating_point_v<T>) {
return value * 0.5; // Only compiled for floating point
} else if constexpr (is_same_v<T, string>) {
return value + value; // Concatenate strings
} else {
static_assert(always_false<T>, "Unsupported type");
}
}
// ═══════════════════════════════════════════════════════════
// Recursive template with if constexpr
// ═══════════════════════════════════════════════════════════
template<typename T, typename... Rest>
void printAll(T first, Rest... rest) {
cout << first;
if constexpr (sizeof...(rest) > 0) {
cout << ", ";
printAll(rest...); // Recursive call
} else {
cout << endl;
}
}
Type Traits
Query and modify type properties at compile time:
Type Queries
#include <type_traits>
// ═══════════════════════════════════════════════════════════
// Primary type categories
// ═══════════════════════════════════════════════════════════
is_void_v<void> // true
is_integral_v<int> // true
is_floating_point_v<double> // true
is_array_v<int[5]> // true
is_pointer_v<int*> // true
is_reference_v<int&> // true
is_function_v<int(int)> // true
is_class_v<MyClass> // true
is_enum_v<MyEnum> // true
// ═══════════════════════════════════════════════════════════
// Type properties
// ═══════════════════════════════════════════════════════════
is_const_v<const int> // true
is_volatile_v<volatile int> // true
is_signed_v<int> // true
is_unsigned_v<unsigned> // true
is_abstract_v<Abstract> // true (has pure virtual)
is_final_v<Final> // true (marked final)
is_empty_v<Empty> // true (no non-static members)
is_polymorphic_v<Poly> // true (has virtual functions)
is_trivial_v<Trivial> // true
is_standard_layout_v<POD> // true
// ═══════════════════════════════════════════════════════════
// Type relationships
// ═══════════════════════════════════════════════════════════
is_same_v<int, int> // true
is_same_v<int, long> // false
is_base_of_v<Base, Derived> // true
is_convertible_v<int, double> // true
is_assignable_v<int&, double> // true
// ═══════════════════════════════════════════════════════════
// Constructibility
// ═══════════════════════════════════════════════════════════
is_default_constructible_v<T>
is_copy_constructible_v<T>
is_move_constructible_v<T>
is_constructible_v<T, Args...> // Can construct T from Args
is_nothrow_constructible_v<T> // noexcept check
Type Modifications
// ═══════════════════════════════════════════════════════════
// Remove qualifiers
// ═══════════════════════════════════════════════════════════
remove_const_t<const int> // int
remove_volatile_t<volatile int> // int
remove_cv_t<const volatile int> // int
remove_reference_t<int&> // int
remove_pointer_t<int*> // int
// Nested removal
using RawType = remove_cv_t<remove_reference_t<const int&>>; // int
// C++20: decay-like but keeps references
remove_cvref_t<const int&> // int
// ═══════════════════════════════════════════════════════════
// Add qualifiers
// ═══════════════════════════════════════════════════════════
add_const_t<int> // const int
add_volatile_t<int> // volatile int
add_cv_t<int> // const volatile int
add_pointer_t<int> // int*
add_lvalue_reference_t<int> // int&
add_rvalue_reference_t<int> // int&&
// ═══════════════════════════════════════════════════════════
// Other transformations
// ═══════════════════════════════════════════════════════════
make_signed_t<unsigned> // int
make_unsigned_t<int> // unsigned int
decay_t<const int&> // int (removes ref, cv, array decay)
common_type_t<int, double> // double
conditional_t<true, int, double> // int
conditional_t<false, int, double> // double
Custom Type Traits
// ═══════════════════════════════════════════════════════════
// Create your own type trait
// ═══════════════════════════════════════════════════════════
// Check if type is a container (has begin/end)
template<typename T, typename = void>
struct is_container : false_type {};
template<typename T>
struct is_container<T, void_t<
decltype(declval<T>().begin()),
decltype(declval<T>().end())
>> : true_type {};
template<typename T>
inline constexpr bool is_container_v = is_container<T>::value;
static_assert(is_container_v<vector<int>>);
static_assert(!is_container_v<int>);
// ═══════════════════════════════════════════════════════════
// Type trait for member detection
// ═══════════════════════════════════════════════════════════
template<typename T, typename = void>
struct has_value_type : false_type {};
template<typename T>
struct has_value_type<T, void_t<typename T::value_type>>
: true_type {};
static_assert(has_value_type<vector<int>>::value); // true
Concepts (C++20)
Named constraints for template parameters:
Built-in Concepts
#include <concepts>
// ═══════════════════════════════════════════════════════════
// Using built-in concepts
// ═══════════════════════════════════════════════════════════
template<integral T> // Concept as constraint
T double_it(T x) { return x * 2; }
template<floating_point T>
T half_it(T x) { return x * 0.5; }
double_it(5); // OK
// double_it(5.5); // Error: not integral
// ═══════════════════════════════════════════════════════════
// Common standard concepts
// ═══════════════════════════════════════════════════════════
same_as<T, U> // T and U are same type
derived_from<D, B> // D derives from B
convertible_to<From, To>
integral<T> // int, short, long, etc.
floating_point<T> // float, double
signed_integral<T> // signed int types
unsigned_integral<T> // unsigned int types
equality_comparable<T> // Has ==
totally_ordered<T> // Has <, >, <=, >=
copyable<T> // Copy constructible & assignable
movable<T> // Move constructible & assignable
default_initializable<T>
invocable<F, Args...> // F can be called with Args
Custom Concepts
// ═══════════════════════════════════════════════════════════
// Define your own concept
// ═══════════════════════════════════════════════════════════
template<typename T>
concept Printable = requires(T t) {
{ cout << t } -> same_as<ostream&>;
};
template<Printable T>
void print(const T& value) {
cout << value << endl;
}
// ═══════════════════════════════════════════════════════════
// Compound requirements
// ═══════════════════════════════════════════════════════════
template<typename T>
concept Hashable = requires(T a) {
{ hash<T>{}(a) } -> convertible_to<size_t>;
};
template<typename T>
concept Container = requires(T c) {
typename T::value_type; // Type requirement
typename T::iterator;
{ c.begin() } -> same_as<typename T::iterator>;
{ c.end() } -> same_as<typename T::iterator>;
{ c.size() } -> convertible_to<size_t>;
{ c.empty() } -> same_as<bool>;
};
// ═══════════════════════════════════════════════════════════
// Combining concepts
// ═══════════════════════════════════════════════════════════
template<typename T>
concept Number = integral<T> || floating_point<T>;
template<typename T>
concept Arithmetic = Number<T> && requires(T a, T b) {
{ a + b } -> same_as<T>;
{ a - b } -> same_as<T>;
{ a * b } -> same_as<T>;
{ a / b } -> same_as<T>;
};
Using Concepts
// ═══════════════════════════════════════════════════════════
// Method 1: Concept as type constraint
// ═══════════════════════════════════════════════════════════
template<integral T>
T increment(T x) { return x + 1; }
// ═══════════════════════════════════════════════════════════
// Method 2: requires clause after template
// ═══════════════════════════════════════════════════════════
template<typename T>
requires integral<T>
T decrement(T x) { return x - 1; }
// ═══════════════════════════════════════════════════════════
// Method 3: Trailing requires clause
// ═══════════════════════════════════════════════════════════
template<typename T>
T negate(T x) requires signed_integral<T> {
return -x;
}
// ═══════════════════════════════════════════════════════════
// Method 4: Abbreviated function template (C++20)
// ═══════════════════════════════════════════════════════════
auto add(integral auto a, integral auto b) {
return a + b;
}
// ═══════════════════════════════════════════════════════════
// Concept-based overloading
// ═══════════════════════════════════════════════════════════
template<integral T>
string describe(T x) { return "integer: " + to_string(x); }
template<floating_point T>
string describe(T x) { return "float: " + to_string(x); }
template<Printable T>
string describe(T x) {
ostringstream oss;
oss << x;
return "printable: " + oss.str();
}
Fold Expressions
C++17 way to apply operator over parameter pack:
Basic Fold Expressions
// ═══════════════════════════════════════════════════════════
// Unary left fold: (... op pack)
// Expands to: ((pack1 op pack2) op pack3) op ...
// ═══════════════════════════════════════════════════════════
template<typename... Args>
auto sum(Args... args) {
return (... + args); // Left fold
}
sum(1, 2, 3, 4, 5); // ((((1+2)+3)+4)+5) = 15
// ═══════════════════════════════════════════════════════════
// Unary right fold: (pack op ...)
// Expands to: pack1 op (pack2 op (pack3 op ...))
// ═══════════════════════════════════════════════════════════
template<typename... Args>
auto power_tower(Args... args) {
return (args ^ ...); // Right fold (for XOR or custom op)
}
// ═══════════════════════════════════════════════════════════
// Binary left fold: (init op ... op pack)
// ═══════════════════════════════════════════════════════════
template<typename... Args>
auto sumWithInit(Args... args) {
return (0 + ... + args); // Start with 0
}
// ═══════════════════════════════════════════════════════════
// Binary right fold: (pack op ... op init)
// ═══════════════════════════════════════════════════════════
template<typename... Args>
bool all_true(Args... args) {
return (args && ... && true);
}
Practical Fold Examples
// ═══════════════════════════════════════════════════════════
// All true?
// ═══════════════════════════════════════════════════════════
template<typename... Args>
bool all(Args... args) {
return (... && args);
}
all(true, true, true); // true
all(true, false, true); // false
// ═══════════════════════════════════════════════════════════
// Any true?
// ═══════════════════════════════════════════════════════════
template<typename... Args>
bool any(Args... args) {
return (... || args);
}
// ═══════════════════════════════════════════════════════════
// Print all
// ═══════════════════════════════════════════════════════════
template<typename... Args>
void printAll(Args&&... args) {
(cout << ... << args) << endl;
}
printAll(1, ", ", 2, ", ", 3); // Output: 1, 2, 3
// Print with separator
template<typename... Args>
void printWithComma(Args&&... args) {
((cout << args << ", "), ...); // Note: trailing comma
cout << endl;
}
// ═══════════════════════════════════════════════════════════
// Call function on each argument
// ═══════════════════════════════════════════════════════════
template<typename F, typename... Args>
void for_each_arg(F f, Args&&... args) {
(f(forward<Args>(args)), ...); // Call f on each
}
for_each_arg([](auto x) { cout << x << " "; }, 1, 2.5, "hi");
// ═══════════════════════════════════════════════════════════
// Push all to container
// ═══════════════════════════════════════════════════════════
template<typename Container, typename... Args>
void push_all(Container& c, Args&&... args) {
(c.push_back(forward<Args>(args)), ...);
}
vector<int> v;
push_all(v, 1, 2, 3, 4, 5); // v = {1, 2, 3, 4, 5}
Constexpr and Templates
constexpr Functions with Templates
// ═══════════════════════════════════════════════════════════
// Compile-time factorial
// ═══════════════════════════════════════════════════════════
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
constexpr int fact5 = factorial(5); // Computed at compile time
static_assert(fact5 == 120);
// ═══════════════════════════════════════════════════════════
// Compile-time array operations
// ═══════════════════════════════════════════════════════════
template<typename T, size_t N>
constexpr T array_sum(const array<T, N>& arr) {
T sum = 0;
for (const auto& x : arr) sum += x;
return sum;
}
constexpr array<int, 5> arr = {1, 2, 3, 4, 5};
constexpr int total = array_sum(arr); // 15 at compile time
// ═══════════════════════════════════════════════════════════
// constexpr if with templates
// ═══════════════════════════════════════════════════════════
template<size_t N>
constexpr auto fib() {
if constexpr (N <= 1) {
return N;
} else {
return fib<N-1>() + fib<N-2>();
}
}
static_assert(fib<10>() == 55);
consteval (C++20)
// ═══════════════════════════════════════════════════════════
// consteval - MUST be evaluated at compile time
// ═══════════════════════════════════════════════════════════
consteval int square(int x) {
return x * x;
}
constexpr int a = square(5); // OK: compile time
// int b = square(runtime_val); // Error: not compile time
// Use for constants that must be computed at compile time
consteval size_t hash_str(const char* str) {
size_t hash = 0;
while (*str) {
hash = hash * 31 + *str++;
}
return hash;
}
// Switch on string hash (compile-time computed)
void process(size_t hash) {
switch (hash) {
case hash_str("hello"): break; // Works!
case hash_str("world"): break;
}
}
Template Metaprogramming
Compile-Time Computation
// ═══════════════════════════════════════════════════════════
// Classic template metaprogramming (before constexpr)
// ═══════════════════════════════════════════════════════════
template<int N>
struct Factorial {
static constexpr int value = N * Factorial<N-1>::value;
};
template<>
struct Factorial<0> {
static constexpr int value = 1;
};
static_assert(Factorial<5>::value == 120);
// ═══════════════════════════════════════════════════════════
// Fibonacci sequence
// ═══════════════════════════════════════════════════════════
template<int N>
struct Fibonacci {
static constexpr int value = Fibonacci<N-1>::value + Fibonacci<N-2>::value;
};
template<> struct Fibonacci<0> { static constexpr int value = 0; };
template<> struct Fibonacci<1> { static constexpr int value = 1; };
static_assert(Fibonacci<10>::value == 55);
// ═══════════════════════════════════════════════════════════
// Type computations
// ═══════════════════════════════════════════════════════════
// Find largest type
template<typename... Ts>
struct LargestType;
template<typename T>
struct LargestType<T> {
using type = T;
};
template<typename T, typename U, typename... Rest>
struct LargestType<T, U, Rest...> {
using type = typename LargestType<
conditional_t<(sizeof(T) >= sizeof(U)), T, U>,
Rest...
>::type;
};
template<typename... Ts>
using largest_t = typename LargestType<Ts...>::type;
static_assert(is_same_v<largest_t<char, int, double>, double>);
Type Lists and Manipulation
// ═══════════════════════════════════════════════════════════
// Type list
// ═══════════════════════════════════════════════════════════
template<typename... Ts>
struct TypeList {
static constexpr size_t size = sizeof...(Ts);
};
// ═══════════════════════════════════════════════════════════
// Get type at index
// ═══════════════════════════════════════════════════════════
template<size_t I, typename List>
struct TypeAt;
template<typename Head, typename... Tail>
struct TypeAt<0, TypeList<Head, Tail...>> {
using type = Head;
};
template<size_t I, typename Head, typename... Tail>
struct TypeAt<I, TypeList<Head, Tail...>> {
using type = typename TypeAt<I-1, TypeList<Tail...>>::type;
};
using MyList = TypeList<int, double, string>;
using Second = typename TypeAt<1, MyList>::type; // double
// ═══════════════════════════════════════════════════════════
// Append to type list
// ═══════════════════════════════════════════════════════════
template<typename List, typename T>
struct Append;
template<typename... Ts, typename T>
struct Append<TypeList<Ts...>, T> {
using type = TypeList<Ts..., T>;
};
using Extended = typename Append<MyList, char>::type;
// TypeList<int, double, string, char>
Advanced Patterns
CRTP (Curiously Recurring Template Pattern)
// ═══════════════════════════════════════════════════════════
// Static polymorphism without virtual functions
// ═══════════════════════════════════════════════════════════
template<typename Derived>
class Counter {
static inline int count = 0;
public:
Counter() { ++count; }
~Counter() { --count; }
static int getCount() { return count; }
};
class Widget : public Counter<Widget> {};
class Gadget : public Counter<Gadget> {};
// Widget and Gadget have independent counters!
// ═══════════════════════════════════════════════════════════
// Mixin classes with CRTP
// ═══════════════════════════════════════════════════════════
template<typename Derived>
class Comparable {
public:
bool operator!=(const Derived& other) const {
return !static_cast<const Derived*>(this)->operator==(other);
}
bool operator<=(const Derived& other) const {
return !(other < *static_cast<const Derived*>(this));
}
// Derived only needs to implement == and <
};
class Point : public Comparable<Point> {
int x, y;
public:
Point(int x, int y) : x(x), y(y) {}
bool operator==(const Point& p) const { return x == p.x && y == p.y; }
bool operator<(const Point& p) const { return x < p.x || (x == p.x && y < p.y); }
};
Tag Dispatching
// ═══════════════════════════════════════════════════════════
// Select implementation based on type properties
// ═══════════════════════════════════════════════════════════
// Implementation for random access iterators (fast)
template<typename Iterator>
void advance_impl(Iterator& it, int n, random_access_iterator_tag) {
it += n; // O(1)
}
// Implementation for bidirectional iterators
template<typename Iterator>
void advance_impl(Iterator& it, int n, bidirectional_iterator_tag) {
if (n > 0) while (n--) ++it;
else while (n++) --it; // O(n)
}
// Implementation for input iterators
template<typename Iterator>
void advance_impl(Iterator& it, int n, input_iterator_tag) {
while (n--) ++it; // O(n), forward only
}
// Public interface - dispatches to correct implementation
template<typename Iterator>
void my_advance(Iterator& it, int n) {
advance_impl(it, n,
typename iterator_traits<Iterator>::iterator_category{});
}
Policy-Based Design
// ═══════════════════════════════════════════════════════════
// Customize behavior through template parameters
// ═══════════════════════════════════════════════════════════
// Policies
struct NullCheck {
template<typename T>
static void check(T* ptr) {
if (!ptr) throw runtime_error("null pointer");
}
};
struct NoCheck {
template<typename T>
static void check(T*) {} // No-op
};
// Policy-based smart pointer
template<typename T, typename CheckPolicy = NullCheck>
class SmartPtr {
T* ptr;
public:
SmartPtr(T* p) : ptr(p) {}
T& operator*() {
CheckPolicy::check(ptr);
return *ptr;
}
};
SmartPtr<int, NullCheck> safe(nullptr);
// *safe; // Throws
SmartPtr<int, NoCheck> fast(nullptr);
// *fast; // Undefined behavior (no check)
Best Practices
✅ Do
// 1. Use if constexpr over SFINAE when possible (C++17)
template<typename T>
auto process(T x) {
if constexpr (is_integral_v<T>) return x * 2;
else return x;
}
// 2. Use concepts for clear constraints (C++20)
template<typename T>
concept Number = is_arithmetic_v<T>;
template<Number T>
T add(T a, T b) { return a + b; }
// 3. Use type aliases for readability
template<typename T>
using RemoveCV = remove_cv_t<remove_reference_t<T>>;
// 4. Use fold expressions for variadic operations (C++17)
template<typename... Args>
auto sum(Args... args) {
return (... + args);
}
// 5. Provide good error messages
template<typename T>
void func(T x) {
static_assert(is_integral_v<T>,
"func() requires integral type");
}
❌ Don't
// 1. Don't overuse template metaprogramming
// Keep it simple when possible
// 2. Don't forget to provide specializations
// When needed for correctness or performance
// 3. Don't ignore compilation time
// Heavy templates slow down compilation
// 4. Don't use raw SFINAE when concepts available
// Concepts are clearer and provide better errors
// 5. Don't forget constexpr
// Many operations can be done at compile time
Quick Reference
// Variadic template
template<typename... Args>
void func(Args... args);
// Sizeof pack
sizeof...(Args)
// Fold expression (C++17)
(... + args) // Sum
(... && args) // All true
// SFINAE
enable_if_t<condition, ReturnType>
// if constexpr (C++17)
if constexpr (is_integral_v<T>) { }
// Type traits
is_integral_v<T> // Check type
remove_const_t<T> // Modify type
// Concepts (C++20)
template<integral T>
void func(T x);
requires integral<T>
// Template template parameter
template<typename T, template<typename> class C>
Compile & Run
g++ -std=c++17 -Wall examples.cpp -o examples && ./examples
# For C++20 concepts: g++ -std=c++20 ...