Docs

README

Advanced Templates in C++

Table of Contents

  1. Introduction
  2. Variadic Templates
  3. Template Template Parameters
  4. SFINAE
  5. Type Traits
  6. Concepts (C++20)
  7. Fold Expressions
  8. Constexpr and Templates
  9. Template Metaprogramming
  10. Advanced Patterns
  11. Best Practices

Introduction

Advanced templates enable powerful compile-time programming:

TechniquePurposeC++ Version
Variadic TemplatesVariable number of argsC++11
SFINAEConditional overloadsC++11
Type TraitsQuery/modify typesC++11
if constexprCompile-time branchingC++17
Fold ExpressionsPack operationsC++17
ConceptsNamed constraintsC++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 ...
README - C++ Tutorial | DeepML