READMEC++

README

Templates / Advanced Templates

Concept Lesson
Intermediate
4 min

Learning Objective

Understand Templates well enough to explain it, recognize it in C++, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

Table Of ContentsVariadic TemplatesBasic SyntaxParameter Pack OperationsPerfect Forwarding With Variadic Templates
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
2 min read18 headings

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 ...

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue