Namespaces
Variants
Views
Actions

Difference between revisions of "cpp/language/constraints"

From cppreference.com
< cpp‎ | language
m (Partial ordering of constraints: fmt: teletyped D1 and D2.)
(split requires expression)
Line 114: Line 114:
  
 
===Constraints===
 
===Constraints===
A constraint is a sequence of logical operations and operands that specifies requirements on template arguments. They can appear within ''requires-expression''s (see below) and directly as bodies of concepts.
+
A constraint is a sequence of logical operations and operands that specifies requirements on template arguments. They can appear within [[cpp/language/requires|requires expressions]] or directly as bodies of concepts.
  
 
There are three types of constraints:
 
There are three types of constraints:
Line 331: Line 331:
 
template<class T>
 
template<class T>
 
void h(T) requires (is_purrable<T>()); // OK
 
void h(T) requires (is_purrable<T>()); // OK
}}
 
 
=== Requires expressions ===
 
The keyword {{c|requires}} is also used to begin a ''requires-expression'', which is a prvalue expression of type {{c|bool}} that describes the constraints on some template arguments. Such an expression is {{tt|true}} if the constraints are satisfied, and {{tt|false}} otherwise:
 
{{source|1=
 
template<typename T>
 
concept Addable = requires (T x) { x + x; }; // requires-expression
 
 
template<typename T> requires Addable<T> // requires-clause, not requires-expression
 
T add(T a, T b) { return a + b; }
 
 
template<typename T>
 
    requires requires (T x) { x + x; } // ad-hoc constraint, note keyword used twice
 
T add(T a, T b) { return a + b; }
 
}}
 
 
The syntax of ''requires-expression'' is as follows:
 
{{sdsc begin}}
 
{{sdsc|{{ttb|requires}} {{ttb|<nowiki>{</nowiki>}} {{spar|requirement-seq}} {{ttb|<nowiki>}</nowiki>}} }}
 
{{sdsc|{{ttb|requires}} {{ttb|(}} {{spar|parameter-list}}{{mark optional}} {{ttb|)}} {{ttb|<nowiki>{</nowiki>}} {{spar|requirement-seq}} {{ttb|<nowiki>}</nowiki>}} }}
 
{{sdsc end}}
 
{{par begin}}
 
{{par | {{spar|parameter-list}} | a comma-separated list of parameters like in a function declaration, except that default arguments are not allowed and it cannot end with an ellipsis (other than one signifying a pack expansion). These parameters have no storage, linkage or lifetime, and are only used to assist in specifying requirements. These parameters are in scope until the closing {{ttb|<nowiki>}</nowiki>}} of the {{spar|requirement-seq}}. }}
 
{{par | {{spar|requirement-seq}} | sequence of ''requirements'', described below (each requirement ends with a semicolon). }}
 
{{par end}}
 
 
Each requirement in the {{spar|requirements-seq}} is one of the following:
 
* simple requirement
 
* type requirements
 
* compound requirements
 
* nested requirements
 
 
Requirements may refer to the template parameters that are in scope, to the local parameters introduced in the {{spar|parameter-list}}, and to any other declarations that are visible from the enclosing context.
 
 
The substitution of template arguments into a requires-expression used in a declaration of a {{rlp|templates#Templated_entity|templated entity}} may result in the formation
 
of invalid types or expressions in its requirements, or the violation of semantic constraints of those requirements. In such cases, the requires-expression evaluates to {{c|false}} and does not cause the program to be ill-formed. The substitution and semantic constraint checking proceeds in lexical order and stops when a condition that determines the result of the requires-expression is encountered. If substitution (if any) and semantic constraint checking succeed, the requires-expression evaluates to {{c|true}}.
 
 
If a substitution failure would occur in a requires-expression for every possible template argument, the program is ill-formed, no diagnostic required:
 
{{source|1=
 
template<class T>
 
concept C = requires
 
{
 
    new int[-(int)sizeof(T)]; // invalid for every T: ill-formed, no diagnostic required
 
};
 
}}
 
 
If a requires-expression contains invalid types or expressions in its requirements, and it does not appear within the declaration of a {{rlp|templates#Templated_entity|templated entity}}, then the program is ill-formed.
 
 
====Simple requirements====
 
A simple requirement is an arbitrary expression statement that does not start with the keyword {{tt|requires}}. It asserts that the expression is valid. The expression is an unevaluated operand; only language correctness is checked.
 
 
{{source|1=
 
template<typename T>
 
concept Addable = requires (T a, T b)
 
{
 
    a + b; // "the expression a+b is a valid expression that will compile"
 
};
 
 
template<class T, class U = T>
 
concept Swappable = requires(T&& t, U&& u)
 
{
 
    swap(std::forward<T>(t), std::forward<U>(u));
 
    swap(std::forward<U>(u), std::forward<T>(t));
 
};
 
}}
 
 
A requirement that starts with the keyword {{tt|requires}} is always interpreted as a nested requirement. Thus a simple requirement cannot start with an unparenthesized requires-expression.
 
 
====Type requirements====
 
A type requirement is the keyword {{c|typename}} followed by a type name, optionally qualified. The requirement is that the named type is valid: this can be used to verify that a certain named nested type exists, or that a class template specialization names a type, or that an alias template specialization names a type. A type requirement naming a class template specialization does not require the type to be complete.
 
 
{{source|1=
 
template<typename T>
 
using Ref = T&;
 
 
template<typename T>
 
concept C = requires
 
{
 
    typename T::inner; // required nested member name
 
    typename S<T>;    // required class template specialization
 
    typename Ref<T>;  // required alias template substitution
 
};
 
 
template<class T, class U>
 
using CommonType = std::common_type_t<T, U>;
 
 
template<class T, class U>
 
concept Common = requires (T&& t, U&& u)
 
{
 
    typename CommonType<T, U>; // CommonType<T, U> is valid and names a type
 
    { CommonType<T, U>{std::forward<T>(t)} };
 
    { CommonType<T, U>{std::forward<U>(u)} };
 
};
 
}}
 
 
====Compound Requirements====
 
A compound requirement has the form
 
{{sdsc begin}}
 
{{sdsc|{{ttb|<nowiki>{</nowiki>}} {{spar|expression}} {{ttb|<nowiki>}</nowiki>}} {{ttb|noexcept}}{{mark optional}} {{spar|return-type-requirement}}{{mark optional}} {{ttb|;}} }}
 
{{sdsc end}}
 
 
{{par begin}}
 
{{par |{{spar|return-type-requirement}} | {{ttb|->}} {{spar|type-constraint}} }}
 
{{par end}}
 
 
and asserts properties of the named expression. Substitution and semantic constraint checking proceeds in the following order:
 
@1@ Template arguments (if any) are substituted into {{spar|expression}};
 
@2@ If {{tt|noexcept}} is used, {{spar|expression}} must not be [[cpp/language/noexcept|potentially throwing]];
 
@3@ If {{spar|return-type-requirement}} is present, then:
 
:@a@ Template arguments are substituted into the {{spar|return-type-requirement}};
 
:@b@ {{c|decltype((expression))}} must satisfy the constraint imposed by the {{spar|type-constraint}}. Otherwise, the enclosing requires-expression is {{c|false}}.
 
 
{{source|1=
 
template<typename T>
 
concept C2 = requires(T x)
 
{
 
    // the expression *x must be valid
 
    // AND the type T::inner must be valid
 
    // AND the result of *x must be convertible to T::inner
 
    {*x} -> std::convertible_to<typename T::inner>;
 
   
 
    // the expression x + 1 must be valid
 
    // AND std::same_as<decltype((x + 1)), int> must be satisfied
 
    // i.e., (x + 1) must be a prvalue of type int
 
    {x + 1} -> std::same_as<int>;
 
   
 
    // the expression x * 1 must be valid
 
    // AND its result must be convertible to T
 
    {x * 1} -> std::convertible_to<T>;
 
};
 
 
}}
 
 
====Nested requirements====
 
 
A nested requirement has the form
 
{{sdsc begin}}
 
{{sdsc| {{ttb|requires}} {{spar|constraint-expression}} {{ttb|;}} }}
 
{{sdsc end}}
 
 
It can be used to specify additional constraints in terms of local parameters. The {{spar|constraint-expression}} must be satisfied by the substituted template arguments, if any. Substitution of template arguments into a nested requirement causes substitution into the {{spar|constraint-expression}} only to the extent needed to determine whether the {{spar|constraint-expression}} is satisfied.
 
 
{{source|1=
 
template<class T>
 
concept Semiregular = DefaultConstructible<T> &&
 
    CopyConstructible<T> && Destructible<T> && CopyAssignable<T> &&
 
requires(T a, size_t n)
 
 
    requires Same<T*, decltype(&a)>; // nested: "Same<...> evaluates to true"
 
    { a.~T() } noexcept; // compound: "a.~T()" is a valid expression that doesn't throw
 
    requires Same<T*, decltype(new T)>; // nested: "Same<...> evaluates to true"
 
    requires Same<T*, decltype(new T[n])>; // nested
 
    { delete new T }; // compound
 
    { delete new T[n] }; // compound
 
};
 
 
}}
 
}}
  
Line 550: Line 395:
 
{{ltt|cpp/keyword/concept}},
 
{{ltt|cpp/keyword/concept}},
 
{{ltt|cpp/keyword/requires}}
 
{{ltt|cpp/keyword/requires}}
 +
 +
===See also===
 +
{{dsc begin}}
 +
{{dsc inc | cpp/language/dsc requires}}
 +
{{dsc end}}
  
 
{{langlinks|de|es|fr|it|ja|pt|ru|zh}}
 
{{langlinks|de|es|fr|it|ja|pt|ru|zh}}

Revision as of 17:56, 7 July 2022

 
 
C++ language
General topics
Flow control
Conditional execution statements
if
Iteration statements (loops)
for
range-for (C++11)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications (until C++17*)
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
explicit (C++11)
static

Special member functions
Templates
Miscellaneous
 
 
This page describes the core language feature adopted for C++20. For named type requirements used in the specification of the standard library, see named requirements. For the Concepts TS version of this feature, see here.

Class templates, function templates, and non-template functions (typically members of class templates) may be associated with a constraint, which specifies the requirements on template arguments, which can be used to select the most appropriate function overloads and template specializations.

Named sets of such requirements are called concepts. Each concept is a predicate, evaluated at compile time, and becomes a part of the interface of a template where it is used as a constraint:

#include <string>
#include <cstddef>
#include <concepts>
 
// Declaration of the concept "Hashable", which is satisfied by any type 'T'
// such that for values 'a' of type 'T', the expression std::hash<T>{}(a)
// compiles and its result is convertible to std::size_t
template<typename T>
concept Hashable = requires(T a)
{
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};
 
struct meow {};
 
// Constrained C++20 function template:
template<Hashable T>
void f(T) {}
//
// Alternative ways to apply the same constraint:
// template<typename T>
//     requires Hashable<T>
// void f(T) {}
//
// template<typename T>
// void f(T) requires Hashable<T> {}
 
int main()
{
    using std::operator""s;
 
    f("abc"s);    // OK, std::string satisfies Hashable
    // f(meow{}); // Error: meow does not satisfy Hashable
}

Violations of constraints are detected at compile time, early in the template instantiation process, which leads to easy to follow error messages:

std::list<int> l = {3, -1, 10};
std::sort(l.begin(), l.end()); 
// Typical compiler diagnostic without concepts:
// invalid operands to binary expression ('std::_List_iterator<int>' and
// 'std::_List_iterator<int>')
//                           std::__lg(__last - __first) * 2);
//                                     ~~~~~~ ^ ~~~~~~~
// ... 50 lines of output ...
//
// Typical compiler diagnostic with concepts:
// error: cannot call std::sort with std::_List_iterator<int>
// note:  concept RandomAccessIterator<std::_List_iterator<int>> was not satisfied

The intent of concepts is to model semantic categories (Number, Range, RegularFunction) rather than syntactic restrictions (HasPlus, Array). According to ISO C++ core guideline T.20, "The ability to specify meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint."

Contents

Concepts

A concept is a named set of requirements. The definition of a concept must appear at namespace scope.

The definition of a concept has the form

template < template-parameter-list >

concept concept-name = constraint-expression;

// concept
template<class T, class U>
concept Derived = std::is_base_of<U, T>::value;

Concepts cannot recursively refer to themselves and cannot be constrained:

template<typename T>
concept V = V<T*>; // error: recursive concept
 
template<class T>
concept C1 = true;
template<C1 T>
concept Error1 = true; // Error: C1 T attempts to constrain a concept definition
template<class T> requires C1<T>
concept Error2 = true; // Error: the requires-clause attempts to constrain a concept

Explicit instantiations, explicit specializations, or partial specializations of concepts are not allowed (the meaning of the original definition of a constraint cannot be changed).

Concepts can be named in an id-expression. The value of the id-expression is true if the constraint expression is satisfied, and false otherwise.

Concepts can also be named in a type-constraint, as part of

In a type-constraint, a concept takes one less template argument than its parameter list demands, because the contextually deduced type is implicitly used as the first argument of the concept.

template<class T, class U>
concept Derived = std::is_base_of<U, T>::value;
 
template<Derived<Base> T>
void f(T); // T is constrained by Derived<T, Base>

Constraints

A constraint is a sequence of logical operations and operands that specifies requirements on template arguments. They can appear within requires expressions or directly as bodies of concepts.

There are three types of constraints:

1) conjunctions
2) disjunctions
3) atomic constraints

The constraint associated with a declaration are determined by normalizing a logical AND expression whose operands are in the following order:

  1. the constraint expression introduced for each constrained type template parameter or non-type template parameter declared with a constrained placeholder type, in order of appearance;
  2. the constraint expression in the requires clause after the template parameter list;
  3. the constraint expression introduced for each parameter with constrained placeholder type in a abbreviated function template declaration;
  4. the constraint expression in the trailing requires clause.

This order determines the order in which constraints are instantiated when checking for satisfaction.

A constrained declaration may only be redeclared using the same syntactic form. No diagnostic is required:

template<Incrementable T>
void f(T) requires Decrementable<T>;
 
template<Incrementable T>
void f(T) requires Decrementable<T>; // OK, redeclaration
 
template<typename T>
    requires Incrementable<T> && Decrementable<T>
void f(T); // ill-formed, no diagnostic required
 
// the following two declarations have different constraints:
// the first declaration has Incrementable<T> && Decrementable<T>
// the second declaration has Decrementable<T> && Incrementable<T>
// Even though they are logically equivalent.
 
template<Incrementable T> 
void g(T) requires Decrementable<T>;
 
template<Decrementable T> 
void g(T) requires Incrementable<T>; // ill-formed, no diagnostic required

Conjunctions

The conjunction of two constraints is formed by using the && operator in the constraint expression:

template<class T>
concept Integral = std::is_integral<T>::value;
template<class T>
concept SignedIntegral = Integral<T> && std::is_signed<T>::value;
template<class T>
concept UnsignedIntegral = Integral<T> && !SignedIntegral<T>;

A conjunction of two constraints is satisfied only if both constraints are satisfied. Conjunctions are evaluated left to right and short-circuited (if the left constraint is not satisfied, template argument substitution into the right constraint is not attempted: this prevents failures due to substitution outside of immediate context).

template<typename T>
constexpr bool get_value() { return T::value; }
 
template<typename T>
    requires (sizeof(T) > 1 && get_value<T>())
void f(T);   // #1
 
void f(int); // #2
 
void g()
{
    f('A'); // OK, calls #2. When checking the constraints of #1,
            // 'sizeof(char) > 1' is not satisfied, so get_value<T>() is not checked
}

Disjunctions

The disjunction of two constraints is formed by using the || operator in the constraint expression.

A disjunction of two constraints is satisfied if either constraint is satisfied. Disjunctions are evaluated left to right and short-circuited (if the left constraint is satisfied, template argument substitution into the right constraint is not attempted).

template<class T = void>
    requires EqualityComparable<T> || Same<T, void>
struct equal_to;

Atomic constraints

An atomic constraint consists of an expression E and a mapping from the template parameters that appear within E to template arguments involving the template parameters of the constrained entity, called its parameter mapping.

Atomic constraints are formed during constraint normalization. E is never a logical AND or logical OR expression (those form conjunctions and disjunctions, respectively).

Satisfaction of an atomic constraint is checked by substituting the parameter mapping and template arguments into the expression E. If the substitution results in an invalid type or expression, the constraint is not satisfied. Otherwise, E, after any lvalue-to-rvalue conversion, shall be a prvalue constant expression of type bool , and the constraint is satisfied if and only if it evaluates to true.

The type of E after substitution must be exactly bool. No conversion is permitted:

template<typename T>
struct S
{
    constexpr operator bool() const { return true; }
};
 
template<typename T>
    requires (S<T>{})
void f(T);   // #1
 
void f(int); // #2
 
void g()
{
    f(0); // error: S<int>{} does not have type bool when checking #1,
          // even though #2 is a better match
}

Two atomic constraints are considered identical if they are formed from the same expression at the source level and their parameter mappings are equivalent.

template<class T>
constexpr bool is_meowable = true;
 
template<class T>
constexpr bool is_cat = true;
 
template<class T>
concept Meowable = is_meowable<T>;
 
template<class T>
concept BadMeowableCat = is_meowable<T> && is_cat<T>;
 
template<class T>
concept GoodMeowableCat = Meowable<T> && is_cat<T>;
 
template<Meowable T>
void f1(T); // #1
 
template<BadMeowableCat T>
void f1(T); // #2
 
template<Meowable T>
void f2(T); // #3
 
template<GoodMeowableCat T>
void f2(T); // #4
 
void g()
{
    f1(0); // error, ambiguous:
           // the is_meowable<T> in Meowable and BadMeowableCat forms distinct atomic
           // constraints that are not identical (and so do not subsume each other)
 
    f2(0); // OK, calls #4, more constrained than #3
           // GoodMeowableCat got its is_meowable<T> from Meowable
}

Constraint normalization

Constraint normalization is the process that transforms a constraint expression into a sequence of conjunctions and disjunctions of atomic constraints. The normal form of an expression is defined as follows:

  • The normal form of an expression (E) is the normal form of E;
  • The normal form of an expression E1 && E2 is the conjunction of the normal forms of E1 and E2.
  • The normal form of an expression E1 || E2 is the disjunction of the normal forms of E1 and E2.
  • The normal form of an expression C<A1, A2, ... , AN>, where C names a concept, is the normal form of the constraint expression of C, after substituting A1, A2, ... , AN for C's respective template parameters in the parameter mappings of each atomic constraint of C. If any such substitution into the parameter mappings results in an invalid type or expression, the program is ill-formed, no diagnostic required.
template<typename T>
concept A = T::value || true;
 
template<typename U>
concept B = A<U*>; // OK: normalized to the disjunction of 
                   // - T::value (with mapping T -> U*) and
                   // - true (with an empty mapping).
                   // No invalid type in mapping even though
                   // T::value is ill-formed for all pointer types
 
template<typename V>
concept C = B<V&>; // Normalizes to the disjunction of
                   // - T::value (with mapping T-> V&*) and
                   // - true (with an empty mapping).
                   // Invalid type V&* formed in mapping => ill-formed NDR
  • The normal form of any other expression E is the atomic constraint whose expression is E and whose parameter mapping is the identity mapping. This includes all fold expressions, even those folding over the && or || operators.

User-defined overloads of && or || have no effect on constraint normalization.

Requires clauses

The keyword requires is used to introduce a requires-clause, which specifies constraints on template arguments or on a function declaration.

template<typename T>
void f(T&&) requires Eq<T>; // can appear as the last element of a function declarator
 
template<typename T> requires Addable<T> // or right after a template parameter list
T add(T a, T b) { return a + b; }

In this case, the keyword requires must be followed by some constant expression (so it's possible to write requires true), but the intent is that a named concept (as in the example above) or a conjunction/disjunction of named concepts or a requires-expression is used.

The expression must have one of the following forms:

  • a primary expression, e.g. Swappable<T>, std::is_integral<T>::value, (std::is_object_v<Args> && ...), or any parenthesized expression
  • a sequence of primary expressions joined with the operator &&
  • a sequence of aforementioned expressions joined with the operator ||
template<class T>
constexpr bool is_meowable = true;
 
template<class T>
constexpr bool is_purrable() { return true; }
 
template<class T>
void f(T) requires is_meowable<T>; // OK
 
template<class T>
void g(T) requires is_purrable<T>(); // error, is_purrable<T>() is not a primary expression
 
template<class T>
void h(T) requires (is_purrable<T>()); // OK

Partial ordering of constraints

Before any further analysis, constraints are normalized by substituting the body of every named concept and every requires expression until what is left is a sequence of conjunctions and disjunctions on atomic constraints.

A constraint P is said to subsume constraint Q if it can be proven that P implies Q up to the identity of atomic constraints in P and Q. (Types and expressions are not analyzed for equivalence: N > 0 does not subsume N >= 0).

Specifically, first P is converted to disjunctive normal form and Q is converted to conjunctive normal form. P subsumes Q if and only if:

  • every disjunctive clause in the disjunctive normal form of P subsumes every conjunctive clause in the conjunctive normal form of Q, where
  • a disjunctive clause subsumes a conjunctive clause if and only if there is an atomic constraint U in the disjunctive clause and an atomic constraint V in the conjunctive clause such that U subsumes V;
  • an atomic constraint A subsumes an atomic constraint B if and only if they are identical using the rules described above.

Subsumption relationship defines partial order of constraints, which is used to determine:

If declarations D1 and D2 are constrained and D1's associated constraints subsume D2's associated constraints (or if D2 is unconstrained), then D1 is said to be at least as constrained as D2. If D1 is at least as constrained as D2, and D2 is not at least as constrained as D1, then D1 is more constrained than D2.

template<typename T>
concept Decrementable = requires(T t) { --t; };
template<typename T>
concept RevIterator = Decrementable<T> && requires(T t) { *t; };
 
// RevIterator subsumes Decrementable, but not the other way around
 
template<Decrementable T>
void f(T); // #1
 
template<RevIterator T>
void f(T); // #2, more constrained than #1
 
f(0);       // int only satisfies Decrementable, selects #1
f((int*)0); // int* satisfies both constraints, selects #2 as more constrained
 
template<class T>
void g(T); // #3 (unconstrained)
 
template<Decrementable T>
void g(T); // #4
 
g(true); // bool does not satisfy Decrementable, selects #3
g(0);    // int satisfies Decrementable, selects #4 because it is more constrained
 
template<typename T>
concept RevIterator2 = requires(T t) { --t; *t; };
 
template<Decrementable T>
void h(T); // #5
 
template<RevIterator2 T>
void h(T); // #6
 
h((int*)0); //ambiguous

Keywords

concept, requires

See also

Requires expression(C++20) yields a prvalue expression of type bool that describes the constraints[edit]