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.

A concept in C++ is a named, compile-time predicate that constrains template parameters. Introduced in C++20, concepts formalize the syntactic and semantic requirements that template arguments must satisfy. They act as an interface for templates, evaluating to a constant boolean expression (true or false). If a template argument fails to satisfy the concept, the compiler rejects the candidate. Concept constraints are evaluated after successful template argument substitution to determine if a candidate is viable, distinguishing this mechanism from SFINAE (Substitution Failure Is Not An Error), which applies to failures during the substitution phase itself.

Defining a Concept

A concept is defined using the concept keyword, followed by an assignment to a constraint expression. The constraint expression must be a core constant expression of type bool.
template <typename T>
concept ConceptName = constraint_expression;
The constraint_expression can be a standard type trait, a logical combination of other concepts using && (conjunction) and || (disjunction), or a requires expression.
#include <type_traits>

// Using a type trait
template <typename T>
concept Integral = std::is_integral_v<T>;

// Logical conjunction of concepts
template <typename T>
concept SignedIntegral = Integral<T> && std::is_signed_v<T>;

The requires Expression

To define complex constraints based on syntax validity, concepts utilize the requires expression. A requires expression checks whether specific code is well-formed without actually evaluating it at runtime.
template <typename T>
concept ComplexConcept = requires(T a, T b) {
    // Requirements go here
};
A requires expression can contain four types of requirements: 1. Simple Requirements Asserts that an expression is valid and will compile. The return type and value are ignored.
requires(T a, T b) {
    a + b;  // The expression 'a + b' must be valid
    a[0];   // The type must support the subscript operator
};
2. Type Requirements Asserts that a specific type exists. It is prefixed with the typename keyword.
requires {
    typename T::value_type; // T must have a nested type named 'value_type'
    typename T::iterator;
};
3. Compound Requirements Asserts the validity of an expression, its exception specification, and/or its return type. The expression is enclosed in braces {}. The return type constraint is evaluated against the exact decltype of the expression.
#include <concepts>

template <typename T>
concept PointerLike = requires(T a, T b) {
    // 'a == b' must be valid, must not throw, and the result must satisfy std::convertible_to<bool>
    { a == b } noexcept -> std::convertible_to<bool>; 
    
    // '*a' must be valid, and its return type must be exactly 'int&'
    // (Dereferencing a typical pointer or iterator yields an lvalue reference)
    { *a } -> std::same_as<int&>;
};
4. Nested Requirements Evaluates an additional compile-time boolean expression. It is prefixed with the requires keyword inside the requires expression block.
requires(T a) {
    requires sizeof(T) >= 4; // The boolean expression must evaluate to true
};

Applying Concepts

Once defined, concepts are applied to templates to constrain their parameters. C++ provides four distinct syntactic forms for applying concepts: 1. Constrained Template Parameter (Type-constraint syntax) The concept name replaces the typename or class keyword in the template parameter list.
template <Integral T>
void process(T value);
2. Trailing requires Clause The constraint is appended to the function signature. This is often used for constraining non-template member functions of class templates.
template <typename T>
void process(T value) requires Integral<T>;
3. Leading requires Clause The constraint is placed immediately after the template parameter list.
template <typename T>
requires Integral<T>
void process(T value);
4. Abbreviated Function Template Syntax The concept is applied directly to an auto parameter in a function signature. The compiler implicitly converts this into a function template.
void process(Integral auto value);

Subsumption and Overload Resolution

Concepts participate directly in function overload resolution through a mechanism called subsumption. If multiple constrained templates match a given function call, the compiler selects the most constrained overload. Concept A subsumes Concept B if A implies B. The compiler determines this by normalizing the constraint expressions into Disjunctive Normal Form (DNF) or Conjunctive Normal Form (CNF) and comparing the atomic constraints.
#include <type_traits>

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

template <typename T>
concept B = A<T> && sizeof(T) == 4;

template <A T> void func(T val); // Overload 1
template <B T> void func(T val); // Overload 2

// If called with a 4-byte integer, Overload 2 is selected 
// because Concept B strictly subsumes Concept A.
Master C++ with Deep Grasping Methodology!Learn More