Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Template constraints are a C++20 language feature that enforces compile-time requirements on template arguments. They restrict the set of types or values that can be substituted into a template parameter list, causing substitution failure if the requirements are not met. This mechanism replaces complex SFINAE (Substitution Failure Is Not An Error) metaprogramming with direct boolean constant expressions, enabling the compiler to perform strict type-checking before template instantiation.

Core Components

The constraint system is built upon three primary language constructs: the requires clause, the concept definition, and the requires expression.

1. The requires Clause

A requires clause appends a boolean constant expression to a template declaration. If the expression evaluates to false, the template is removed from the overload resolution set.
#include <type_traits>

template <typename T>
requires std::is_integral_v<T>
void process(T value);

2. Concepts

A concept is a named set of constraints. It is defined at namespace scope and evaluates to a boolean prvalue. Concepts cannot be recursively defined and cannot be explicitly instantiated.
#include <type_traits>

template <typename T>
concept Integral = std::is_integral_v<T>;

template <typename T>
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;

3. Application Syntax

Constraints can be applied to templates in four distinct syntactic forms, all of which are semantically equivalent:
// 1. Standard requires clause
template <typename T> 
requires Integral<T>
void compute(T a);

// 2. Trailing requires clause
template <typename T>
void compute(T a) requires Integral<T>;

// 3. Constrained template parameter (Terse syntax)
template <Integral T>
void compute(T a);

// 4. Abbreviated function template syntax
void compute(Integral auto a);

The requires Expression

A requires expression is a distinct language feature from the requires clause. It is an operator that yields a boolean prvalue based on whether a set of hypothetical expressions and types are well-formed. It does not evaluate the expressions at runtime. A requires expression can contain four types of requirements:
  1. Simple Requirements: Asserts that an expression is valid (compiles).
  2. Type Requirements: Asserts that a nested type or class template specialization exists.
  3. Compound Requirements: Asserts expression validity, checks noexcept status, and constrains the return type.
  4. Nested Requirements: Evaluates additional boolean constant expressions.
#include <concepts>
#include <cstddef>

template <typename T>
concept Container = requires(T a) {
    // 1. Simple requirement: expression must be well-formed
    a.clear(); 

    // 2. Type requirement: type must exist
    typename T::value_type; 

    // 3. Compound requirement: expression must be valid, 
    // must not throw, and return type must satisfy std::same_as
    { a.size() } noexcept -> std::same_as<std::size_t>; 

    // 4. Nested requirement: boolean expression must be true
    requires sizeof(typename T::value_type) >= 4; 
};

Constraint Normalization and Subsumption

When multiple constrained templates are viable during overload resolution, the compiler determines the best match through a process called subsumption. To perform subsumption, the compiler normalizes constraints by expanding concept definitions into a structural tree of conjunctions (logical ANDs) and disjunctions (logical ORs) of atomic constraints. A constraint PP subsumes a constraint QQ if PP implies QQ. The compiler will select the most constrained viable template.
template <typename T> 
concept Sized = requires(T x) { x.size(); };

template <typename T> 
concept SizedAndClearable = Sized<T> && requires(T x) { x.clear(); };

// Overload 1
template <Sized T> 
void manipulate(T val);

// Overload 2: Subsumes Overload 1 because SizedAndClearable implies Sized
template <SizedAndClearable T> 
void manipulate(T val); 
Subsumption only works with named concept definitions. Inline requires clauses using identical type traits do not subsume one another because the compiler considers atomic constraints identical only if they originate from the same appearance of the same expression in the source code (i.e., the exact same lexical location). They are not evaluated by structural equivalence.
Master C++ with Deep Grasping Methodology!Learn More