Template Metaprogramming (TMP) is a technique in C++ where templates are used to perform computation at Compile-Time. The compiler acts as an interpreter: template instantiation is Turing-complete, so any computable function can (in principle) be evaluated before the program runs.

The core idea is that types and constant expressions are the “values” of a compile-time language, and template specialization is its “branching.”

Topic List

Type Traits

Type Trait

A type trait is a Template Metaprogramming pattern in C++. It is a template struct that exposes a Compile-Time fact about a type, typically through a static constexpr member called value or a member type alias called type. It is equivalent to Rust’s trait bounds.

Example

// Is T a pointer?
template <typename T>
struct is_pointer {
    static constexpr bool value = false;
};
 
// Partial specialization: T* is a pointer
template <typename T>
struct is_pointer<T*> {
    static constexpr bool value = true;
};
 
static_assert(is_pointer<int>::value == false);
static_assert(is_pointer<int*>::value == true);

The standard library provides dozens of these in <type_traits>: std::is_integral, std::is_same, std::remove_const, std::decay, etc.

Convenience Aliases

By convention, traits that produce a type have a _t alias, and traits that produce a value have a _v alias:

// Instead of: typename std::remove_const<T>::type
std::remove_const_t<T>
 
// Instead of: std::is_integral<T>::value
std::is_integral_v<T>
Link to original

SFINAE

Substitution Failure Is Not An Error

Substitution Failure Is Not An Error (SFINAE) is a Template Metaprogramming rule in C++: when the compiler substitutes template arguments and the result is ill-formed, that specialization is silently discarded from the overload set rather than producing a compile error.

Warning

SFINAE has largely been superceded by if constexpr and Concepts. Use with caution!

Example

// Only enabled when T is an integral type
template <typename T>
auto to_string(T value) -> std::enable_if_t<std::is_integral_v<T>, std::string> {
    return std::to_string(value);
}
 
// Only enabled when T has a .str() method
template <typename T>
auto to_string(T value) -> decltype(value.str()) {
    return value.str();
}

If T = double, the first overload’s enable_if_t produces an invalid type and is discarded. If T = int, the second overload’s decltype(value.str()) is ill-formed and is discarded. No error, the compiler just picks whichever overload survives.

enable_if

The standard mechanism for SFINAE:

template <bool Condition, typename T = void>
struct enable_if {};           // no 'type' member when false
 
template <typename T>
struct enable_if<true, T> {
    using type = T;            // 'type' exists only when true
};
Link to original

constexpr Functions

constexpr Functions

A constexpr function can be evaluated at compile-time when given constant arguments, or at runtime otherwise.

constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}
 
static_assert(factorial(5) == 120);  // computed at compile-time
int x = factorial(runtime_value);    // computed at runtime
Link to original

Circular transclusion detected: Computer-Science/Architecture/Programming-Languages/Languages/C++/Template-Metaprogramming/constexpr

Concepts

Concept

A concept (C++20) is a named set of constraints on a template parameter in C++. It is the Template Metaprogramming equivalent of a Rust trait bound.

Example

template <typename T>
concept Numeric = std::is_arithmetic_v<T>;
 
template <Numeric T>
T add(T a, T b) { return a + b; }
 
add(1, 2);       // ok
add("a", "b");   // compile error: const char* does not satisfy Numeric

Defining Concepts

Concepts can compose type traits, expressions, and other concepts:

template <typename T>
concept Printable = requires(T t) {
    { std::cout << t } -> std::same_as<std::ostream&>;
};
 
template <typename T>
concept Summable = requires(T a, T b) {
    { a + b } -> std::convertible_to<T>;
};

The requires expression lists what the type must support. If any expression inside is ill-formed, the concept evaluates to false.

Syntax Variants

// Shorthand: concept as type constraint
template <Numeric T>
T square(T x) { return x * x; }
 
// Requires clause
template <typename T>
requires Numeric<T>
T square(T x) { return x * x; }
 
// Trailing requires
template <typename T>
T square(T x) requires Numeric<T> { return x * x; }
 
// Abbreviated function template (auto)
auto square(Numeric auto x) { return x * x; }

Concepts are the closest C++ gets to Rust traits, but they only constrain; Rust traits also provide default implementations.

Link to original

Variadic Template Parameter Pack

Variadic Template Parameter Pack

A Variadic Template Parameter Pack is a Template Metaprogramming pattern in C++ that lets a function accept any number of arguments of any types, resolved at Compile-Time.

Syntax

template <typename... Args>
void foo(Args... args) {
    bar(args...);  // pack expansion -- forwards all args to bar
}
  • typename… Args declares a template parameter pack (zero or more types)
  • Args… args declares a function parameter pack (one parameter per type in Args)
  • args… is a pack expansion (the compiler substitutes each element)

How it works

Each call site generates a unique specialization:

foo(42, "hello", 3.14);
// Compiler generates:
// void foo(int args0, const char* args1, double args2) {
//     bar(args0, args1, args2);
// }

This is zero-cost: no runtime dispatch, no type erasure. The compiler produces exactly the code you’d write by hand for each combination of argument types.

Patterns

Fold

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // right fold: a + (b + (c + d))
}

sizeof

template <typename... Args>
constexpr std::size_t count(Args...) {
    return sizeof...(Args);  // number of types in the pack
}
Link to original

Curiously Recurring Template Pattern

Curiously Recurring Template Pattern

In C++, a Curiously Recurring Template Pattern is an idiomatic approach to achieving Static Polymorphism, where a class derives from a class Template instantiation using itself as the template argument.

template <typename Derived>
struct Shape {
    double area() const {
        return static_cast<const Derived*>(this)->area_impl();
    }
};
 
struct Circle : Shape<Circle> {
    double radius;
    double area_impl() const { return 3.14159 * radius * radius; }
};
 
struct Square : Shape<Square> {
    double side;
    double area_impl() const { return side * side; }
};
Link to original