,
(including
), and other
(typically members of class templates) might 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
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:
Run this code
#include<cstddef>#include<concepts>#include<functional>#include<string>// 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_ttemplate<typenameT>conceptHashable=requires(Ta){{std::hash<T>{}(a)}->std::convertible_to<std::size_t>;};structmeow{};// Constrained C++20 function template:template<HashableT>voidf(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> {}//// void f(Hashable auto /* parameter-name */) {}intmain(){usingstd::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 satisfiedThe intent of concepts is to model semantic categories (Number, Range, RegularFunction) rather than syntactic restrictions (HasPlus, Array). According to
, “The ability to specify meaningful semantics is a defining characteristic of a true concept, as opposed to a syntactic constraint.”
Concepts
A concept is a named set of
. The definition of a concept must appear at namespace scope.
The definition of a concept has the form
template <template-parameter-list>conceptconcept-name attr(optional)=constraint-expression;
// concepttemplate<classT,classU>conceptDerived=std::is_base_of<U,T>::value;Concepts cannot recursively refer to themselves and cannot be constrained:
template<typenameT>conceptV=V<T*>;// error: recursive concepttemplate<classT>conceptC1=true;template<C1T>conceptError1=true;// Error: C1 T attempts to constrain a concept definitiontemplate<classT>requiresC1<T>conceptError2=true;// Error: the requires clause attempts to constrain a conceptExplicit 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
type template parameter declaration
,
,
.
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<classT,classU>conceptDerived=std::is_base_of<U,T>::value;template<Derived<Base>T>voidf(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
or directly as bodies of concepts.
There are three(until C++26)four(since C++26) types of constraints:
1) conjunctions
2) disjunctions
3) atomic constraints
4) fold expanded constraints
(since C++26)The constraint associated with a declaration is determined by
a logical AND expression whose operands are in the following order:
the constraint expression introduced for each constrained
or non-type template parameter declared with a constrained
, in order of appearance;
the constraint expression in the
after the template parameter list;
the constraint expression introduced for each parameter with constrained
in an
declaration;
the constraint expression in the trailing
.
This order determines the order in which constraints are instantiated when checking for satisfaction.
Redeclarations
A constrained declaration may only be redeclared using the same syntactic form. No diagnostic is required:
// These first two declarations of f are finetemplate<IncrementableT>voidf(T)requiresDecrementable<T>;template<IncrementableT>voidf(T)requiresDecrementable<T>;// OK, redeclaration// Inclusion of this third, logically-equivalent-but-syntactically-different// declaration of f is ill-formed, no diagnostic requiredtemplate<typenameT>requiresIncrementable<T>&&Decrementable<T>voidf(T);// 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<IncrementableT>voidg(T)requiresDecrementable<T>;template<DecrementableT>voidg(T)requiresIncrementable<T>;// ill-formed, no diagnostic requiredConjunctions
The conjunction of two constraints is formed by using the && operator in the constraint expression:
template<classT>conceptIntegral=std::is_integral<T>::value;template<classT>conceptSignedIntegral=Integral<T>&&std::is_signed<T>::value;template<classT>conceptUnsignedIntegral=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<typenameT>constexprboolget_value(){returnT::value;}template<typenameT>requires(sizeof(T)>1&&get_value<T>())voidf(T);// #1voidf(int);// #2voidg(){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<classT=void>requiresEqualityComparable<T>||Same<T,void>structequal_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
. 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, must 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<typenameT>structS{constexproperatorbool()const{returntrue;}};template<typenameT>requires(S<T>{})voidf(T);// #1voidf(int);// #2voidg(){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<classT>constexprboolis_meowable=true;template<classT>constexprboolis_cat=true;template<classT>conceptMeowable=is_meowable<T>;template<classT>conceptBadMeowableCat=is_meowable<T>&&is_cat<T>;template<classT>conceptGoodMeowableCat=Meowable<T>&&is_cat<T>;template<MeowableT>voidf1(T);// #1template<BadMeowableCatT>voidf1(T);// #2template<MeowableT>voidf2(T);// #3template<GoodMeowableCatT>voidf2(T);// #4voidg(){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}Fold expanded constraints
A fold expanded constraint is formed from a constraint C and a fold operator (either && or ||). A fold expanded constraint is a
.
Let N be the number of elements in the pack expansion parameters:
If the pack expansion is invalid (such as expanding packs of different size), the fold expanded constraint is not satisfied.
If N is 0, the fold expanded constraint is satisfied if the fold operator is &&, or not satisfied if the fold operator is ||.
For a fold expanded constraint with a positive N,for each i in [1, N], each pack expansion parameter is replaced with the corresponding ith element in increasing order:
For fold expanded constraints whose fold operator is &&, if the replacement of the jth element violates C, the fold expanded constraint is not satisfied. In this case, no substitution takes place for any i greater than j. Otherwise, the fold expanded constraint is satisfied.
For fold expanded constraints whose fold operator is ||, if the replacement of the jth element satisfies C, the fold expanded constraint is satisfied. In this case, no substitution takes place for any i greater than j. Otherwise, the fold expanded constraint is not satisfied.
template<classT>conceptA=std::is_move_constructible_v<T>;template<classT>conceptB=std::is_copy_constructible_v<T>;template<classT>conceptC=A<T>&&B<T>;// in C++23, these two overloads of g() have distinct atomic constraints // that are not identical and so do not subsume each other: calls to g() are ambiguous// in C++26, the folds are expanded and constraint on overload #2 (both move and copy// required), subsumes constraint on overload #1 (just the move is required)template<class...T>requires(A<T>&&...)voidg(T...);// #1template<class...T>requires(C<T>&&...)voidg(T...);// #2(since C++26)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<typenameT>conceptA=T::value||true;template<typenameU>conceptB=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 typestemplate<typenameV>conceptC=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 NDRThe normal form of expressions (E&&...) and (...&&E) is a fold expanded constraint, where C is the normal form of E and the fold operator is &&.
The normal form of expressions (E||...) and (...||E) is a fold expanded constraint, where C is the normal form of E and the fold operator is ||.
The normal forms of expressions (E1&&...&&E2) and (E1||...||E2) are the normal forms of
(E1&&...)&&E2 and (E1||...)||E2 respectively, if E1 contains an unexpanded pack, or
E1&&(...&&E2) and E1||(...||E2) respectively otherwise.
(since C++26)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
, even those folding over the && or || operators.
User-defined overloads of && or || have no effect on constraint normalization.
requires clauses
The keyword
is used to introduce a requires clause , which specifies constraints on template arguments or on a function declaration.
template<typenameT>voidf(T&&)requiresEq<T>;// can appear as the last element of a function declaratortemplate<typenameT>requiresAddable<T>// or right after a template parameter listTadd(Ta,Tb){returna+b;}In this case, the keyword requires must be followed by some constant expression (so it's possible to write requirestrue), but the intent is that a named concept (as in the example above) or a conjunction/disjunction of named concepts or a
is used.
The expression must have one of the following forms:
A
, 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<classT>constexprboolis_meowable=true;template<classT>constexprboolis_purrable(){returntrue;}template<classT>voidf(T)requiresis_meowable<T>;// OKtemplate<classT>voidg(T)requiresis_purrable<T>();// error, is_purrable<T>() is not a primary expressiontemplate<classT>voidh(T)requires(is_purrable<T>());// OKPartial ordering of constraints
Before any further analysis, constraints are
by substituting the body of every named concept and every
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
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
.
A fold expanded constraint A subsumes another fold expanded constraint B if they have the same fold operator, the constraint C of A subsumes that of B, and both C contain an equivalent unexpanded pack.
(since C++26)Subsumption relationship defines partial order of constraints, which is used to determine:
the best viable candidate for a non-template function in
the
address of a non-template function
in an overload set
the best match for a template template argument
partial ordering of class template specializations
of function templates
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.
If all following conditions are satisfied, a non-template function F1 is more partial-ordering-constrained than a non-template function F2:
They have the same parameter-type-list, omitting the types of
(since C++23).
If they are member functions, both are direct members of the same class.
If both are non-static member functions, they have the same types for their object parameters.
F1 is more constrained than F2.
template<typenameT>conceptDecrementable=requires(Tt){--t;};template<typenameT>conceptRevIterator=Decrementable<T>&&requires(Tt){*t;};// RevIterator subsumes Decrementable, but not the other way aroundtemplate<DecrementableT>voidf(T);// #1template<RevIteratorT>voidf(T);// #2, more constrained than #1f(0);// int only satisfies Decrementable, selects #1f((int*)0);// int* satisfies both constraints, selects #2 as more constrainedtemplate<classT>voidg(T);// #3 (unconstrained)template<DecrementableT>voidg(T);// #4g(true);// bool does not satisfy Decrementable, selects #3g(0);// int satisfies Decrementable, selects #4 because it is more constrainedtemplate<typenameT>conceptRevIterator2=requires(Tt){--t;*t;};template<DecrementableT>voidh(T);// #5template<RevIterator2T>voidh(T);// #6h((int*)0);// ambiguousNotes
Feature-test macro ValueStdFeature
(C++20)Constraints
(C++20)Conditionally trivial
(C++26)
involving
Keywords
,
,
Defect reports
The following behavior-changing defect reports were applied retroactively to previously published C++ standards.
DR Applied to Behavior as published Correct behavior
C++20 could not apply attributes to concepts allowed See also