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.

The requires keyword in C++20 introduces compile-time constraints on template parameters, restricting the set of types or values that can be substituted during template instantiation. It operates by evaluating boolean constant expressions or checking the well-formedness of expressions and types. If a constraint is not satisfied, the template is discarded from the overload resolution set. Crucially, constraint checking is a distinct phase that occurs after template argument substitution has successfully completed. Therefore, discarding a candidate due to an unsatisfied requires clause is a constraint failure, not a SFINAE (Substitution Failure Is Not An Error) failure. In C++, requires manifests in two distinct but interacting grammatical constructs: the requires clause and the requires expression.

The Requires Clause

A requires clause is appended to a template declaration or a function declaration to specify the actual constraints. It must evaluate to a boolean constant expression (true or false). A requires clause can be positioned in two places:
  1. Immediately following the template parameter list.
  2. As a trailing requires clause, following the function declarator.
// 1. Following the template parameter list
template <typename T>
requires std::integral<T>
void process(T value);

// 2. Trailing requires clause
template <typename T>
void process(T value) requires std::integral<T>;

The Requires Expression

A requires expression is a boolean prvalue expression of type bool that evaluates whether a specific set of syntactic requirements are well-formed. It does not execute code; it operates entirely in an unevaluated context (similar to decltype or sizeof).
requires (parameter-list) {
    requirement-sequence;
}
The parameter-list declares local variables that are used exclusively within the requirement-sequence to test operations.

Types of Requirements

Inside the body of a requires expression, you can define four distinct types of requirements:

1. Simple Requirements

A simple requirement is an arbitrary expression statement. The compiler checks only if the expression is well-formed (i.e., it compiles). It does not evaluate the result or check the return type.
requires (T a, T b) {
    a + b;     // Requirement: The addition operator must be valid for type T
    a.clear(); // Requirement: Type T must have a clear() member function
};

2. Type Requirements

Introduced by the typename keyword, a type requirement asserts that a specific nested type, type alias, or class template specialization is valid and exists. It only checks if the type can be formed.
requires {
    typename T::value_type; // Requirement: T must have a nested 'value_type'
    typename T::iterator;   // Requirement: T must have a nested 'iterator'
};

3. Compound Requirements

A compound requirement is enclosed in braces {} and allows you to constrain the return type of an expression and/or assert that the expression does not throw exceptions (noexcept).
requires (T a, T b) {
    // Requirement: a == b must be well-formed AND return a type convertible to bool
    { a == b } -> std::convertible_to<bool>;

    // Requirement: a.compute() must be well-formed AND marked noexcept
    { a.compute() } noexcept;

    // Requirement: Both noexcept and return type constraints applied
    { a.size() } noexcept -> std::same_as<std::size_t>;
};

4. Nested Requirements

A nested requirement is introduced by an additional requires keyword followed by a boolean constant expression. Unlike simple requirements that check if an expression is well-formed, a nested requirement checks if an expression evaluates to true.
requires {
    // Requirement: The size of T must be exactly 4 bytes
    requires sizeof(T) == 4; 
    
    // Requirement: T must satisfy another concept
    requires std::is_trivial_v<T>;
};

The requires requires Idiom

Because a requires clause expects a boolean expression, and a requires expression yields a boolean, they are frequently combined to create ad-hoc constraints directly on a template without defining a standalone Concept. The first requires initiates the clause, and the second requires initiates the expression.
template <typename T>
requires requires(T x) { 
    x.serialize(); 
    typename T::iterator;
}
void transmit(T data);
In this construct, the template transmit is only viable if T possesses a serialize() method and a nested iterator type.
Master C++ with Deep Grasping Methodology!Learn More