Files
2025-10-25 03:02:53 +03:00

43 KiB
Raw Permalink Blame History

[temp.constr]

13 Templates [temp]

13.5 Template constraints [temp.constr]

13.5.1 General [temp.constr.general]

1

#

[Note 1:

Subclause [temp.constr] defines the meaning of constraints on template arguments.

The abstract syntax and satisfaction rules are defined in [temp.constr.constr].

Constraints are associated with declarations in [temp.constr.decl].

Declarations are partially ordered by their associated constraints ([temp.constr.order]).

— end note]

13.5.2 Constraints [temp.constr.constr]

13.5.2.1 General [temp.constr.constr.general]

1

#

A constraint is a sequence of logical operations and operands that specifies requirements on template arguments.

The operands of a logical operation are constraints.

There are five different kinds of constraints:

conjunctions ([temp.constr.op]),

disjunctions ([temp.constr.op]),

atomic constraints ([temp.constr.atomic]),

concept-dependent constraints ([temp.constr.concept]), and

fold expanded constraints ([temp.constr.fold]).

2

#

In order for a constrained template to be instantiated ([temp.spec]), its associated constraints shall be satisfied as described in the following subclauses.

[Note 1:

Forming the name of a specialization of a class template, a variable template, or an alias template ([temp.names]) requires the satisfaction of its constraints.

Overload resolution requires the satisfaction of constraints on functions and function templates.

— end note]

13.5.2.2 Logical operations [temp.constr.op]

1

#

There are two binary logical operations on constraints: conjunction and disjunction.

[Note 1:

These logical operations have no corresponding C++ syntax.

For the purpose of exposition, conjunction is spelled using the symbol ∧ and disjunction is spelled using the symbol ∨ .

The operands of these operations are called the left and right operands.

In the constraint A ∧ B,A is the left operand, and B is the right operand.

— end note]

2

#

A conjunction is a constraint taking two operands.

To determine if a conjunction issatisfied, the satisfaction of the first operand is checked.

If that is not satisfied, the conjunction is not satisfied.

Otherwise, the conjunction is satisfied if and only if the second operand is satisfied.

3

#

A disjunction is a constraint taking two operands.

To determine if a disjunction issatisfied, the satisfaction of the first operand is checked.

If that is satisfied, the disjunction is satisfied.

Otherwise, the disjunction is satisfied if and only if the second operand is satisfied.

4

#

[Example 1: templateconstexpr bool get_value() { return T::value; }templaterequires (sizeof(T) > 1) && (get_value())void f(T); // has associated constraint sizeof(T) > 1 ∧ get_value()void f(int);

f('a'); // OK, calls f(int)

In the satisfaction of the associated constraints of f, the constraint sizeof(char) > 1 is not satisfied; the second operand is not checked for satisfaction.

— end example]

5

#

[Note 2:

A logical negation expression ([expr.unary.op]) is an atomic constraint; the negation operator is not treated as a logical operation on constraints.

As a result, distinct negation constraint-expressions that are equivalent under [temp.over.link] do not subsume one another under [temp.constr.order].

Furthermore, if substitution to determine whether an atomic constraint is satisfied ([temp.constr.atomic]) encounters a substitution failure, the constraint is not satisfied, regardless of the presence of a negation operator.

[Example 2: template concept sad = false;

template int f1(T) requires (!sad);template int f1(T) requires (!sad) && true;int i1 = f1(42); // ambiguous, !sad atomic constraint expressions ([temp.constr.atomic])// are not formed from the same expressiontemplate concept not_sad = !sad;template int f2(T) requires not_sad;template int f2(T) requires not_sad && true;int i2 = f2(42); // OK, !sad atomic constraint expressions both come from not_sadtemplate int f3(T) requires (!sad);int i3 = f3(42); // error: associated constraints not satisfied due to substitution failuretemplate concept sad_nested_type = sad;template int f4(T) requires (!sad_nested_type);int i4 = f4(42); // OK, substitution failure contained within sad_nested_type

Here,requires (!sad<typename T::type>) requires that there is a nested type that is not sad, whereasrequires (!sad_nested_type) requires that there is no sad nested type.

— end example]

— end note]

13.5.2.3 Atomic constraints [temp.constr.atomic]

1

#

An atomic constraint is formed from an expression E and a mapping from the template parameters that appear within E to template arguments that are formed via substitution during constraint normalization in the declaration of a constrained entity (and, therefore, can involve the unsubstituted template parameters of the constrained entity), called the parameter mapping ([temp.constr.decl]).

[Note 1:

Atomic constraints are formed by constraint normalization.

E is never a logical and expression nor a logical or expression.

— end note]

2

#

Two atomic constraints, e1 and e2, areidentical if they are formed from the same appearance of the sameexpression and if, given a hypothetical template A whose template-parameter-list consists oftemplate-parameters corresponding and equivalent ([temp.over.link]) to those mapped by the parameter mappings of the expression, a template-id naming A whose template-arguments are the targets of the parameter mapping of e1 is the same ([temp.type]) as a template-id naming A whose template-arguments are the targets of the parameter mapping of e2.

[Note 2:

The comparison of parameter mappings of atomic constraints operates in a manner similar to that of declaration matching with alias template substitution ([temp.alias]).

[Example 1: template constexpr bool Atomic = true;template concept C = Atomic;template concept Add1 = C<N + 1>;template concept AddOne = C<N + 1>;template void f()requires Add1<2 * M>;template int f()requires AddOne<2 * M> && true;

int x = f<0>(); // OK, the atomic constraints from concept C in both fs are Atomic// with mapping similar to N↦2 * M + 1template struct WrapN;template using Add1Ty = WrapN<N + 1>;template using AddOneTy = WrapN<N + 1>;template void g(Add1Ty<2 * M> *);template void g(AddOneTy<2 * M> *);

void h() { g<0>(nullptr); // OK, there is only one g} — end example]

As specified in [temp.over.link], if the validity or meaning of the program depends on whether two constructs are equivalent, and they are functionally equivalent but not equivalent, the program is ill-formed, no diagnostic required.

[Example 2: template void f2()requires Add1<2 * N>;template int f2()requires Add1<N * 2> && true;void h2() { f2<0>(); // ill-formed, no diagnostic required:// requires determination of subsumption between atomic constraints that are// functionally equivalent but not equivalent} — end example]

— end note]

3

#

To determine if an atomic constraint issatisfied, the parameter mapping and template arguments are first substituted into its expression.

If substitution results in an invalid type or expression in the immediate context of the atomic constraint ([temp.deduct.general]), the constraint is not satisfied.

Otherwise, the lvalue-to-rvalue conversion is performed if necessary, and E shall be a constant expression of type bool.

The constraint is satisfied if and only if evaluation of E results in true.

If, at different points in the program, the satisfaction result is different for identical atomic constraints and template arguments, the program is ill-formed, no diagnostic required.

[Example 3: template concept C =sizeof(T) == 4 && !true; // requires atomic constraints sizeof(T) == 4 and !truetemplate struct S {constexpr operator bool() const { return true; }};

template requires (S{})void f(T); // #1void f(int); // #2void g() { f(0); // error: expression S{} does not have type bool} // while checking satisfaction of deduced arguments of #1;// call is ill-formed even though #2 is a better match — end example]

13.5.2.4 Concept-dependent constraints [temp.constr.concept]

1

#

A concept-dependent constraint CD is an atomic constraint whose expression is a concept-id CI whoseconcept-name names a dependent concept named C.

2

#

To determine if CD issatisfied, the parameter mapping and template arguments are first substituted into C.

If substitution results in an invalid concept-id in the immediate context of the constraint ([temp.deduct.general]), the constraint is not satisfied.

Otherwise, let CI′ be the normal form ([temp.constr.normal]) of the concept-id after substitution of C.

[Note 1:

Normalization of CI might be ill-formed; no diagnostics is required.

— end note]

3

#

To form CI′′, each appearance of C's template parameters in the parameter mappings of the atomic constraints (including concept-dependent constraints) in CI′ is substituted with their respective arguments from the parameter mapping of CD and the arguments of CI.

4

#

CD is satisfied if CI′′ is satisfied.

[Note 2:

Checking whether CI′′ is satisfied can lead to further normalization of concept-dependent constraints.

— end note]

[Example 1: templateconcept C = true;

template<typename T, template concept CC>concept D = CC;

template<typename U, template concept CT, template<typename, template concept> concept CU>int f() requires CU<U, CT>;int i = f<int, C, D>();

In this example, the associated constraints of f consist of a concept-dependent constraint whose expression is the concept-id CU<U, CT> with the mappingU↦U,CT↦CT,CU↦CU.

The result of substituting D into this expression is D<U, CT>.

We consider the normal form of the resulting concept-id, which is CC with the mappingT↦U,CC↦CT.

By recursion, C is substituted into CC, and the result is normalized to the atomic constraint true, which is satisfied.

— end example]

13.5.2.5 Fold expanded constraint [temp.constr.fold]

1

#

A fold expanded constraint is formed from a constraint C and a fold-operator which can either be && or ||.

A fold expanded constraint is a pack expansion ([temp.variadic]).

Let N be the number of elements in the pack expansion parameters ([temp.variadic]).

2

#

A fold expanded constraint whose fold-operator is && is satisfied if it is a valid pack expansion and if N=0 or if for each i where 0≤i<N in increasing order,C is satisfied when replacing each pack expansion parameter with the corresponding ith element.

No substitution takes place for any i greater than the smallest i for which the constraint is not satisfied.

3

#

A fold expanded constraint whose fold-operator is || is satisfied if it is a valid pack expansion,N>0, and if for i where 0≤i<N in increasing order, there is a smallest i for which C is satisfied when replacing each pack expansion parameter with the corresponding ith element.

No substitution takes place for any i greater than the smallest i for which the constraint is satisfied.

4

#

[Note 1:

If the pack expansion expands packs of different size, then it is invalid and the fold expanded constraint is not satisfied.

— end note]

5

#

Two fold expanded constraints are compatible for subsumption if their respective constraints both contain an equivalent unexpanded pack ([temp.over.link]).

13.5.3 Constrained declarations [temp.constr.decl]

1

#

A template declaration ([temp.pre]) or templated function declaration ([dcl.fct]) can be constrained by the use of a requires-clause.

This allows the specification of constraints for that declaration as an expression:

constraint-expression:
logical-or-expression

2

#

Constraints can also be associated with a declaration through the use oftype-constraints in a template-parameter-list or parameter-type-list.

Each of these forms introduces additional constraint-expressions that are used to constrain the declaration.

3

#

A declaration's associated constraints are defined as follows:

the constraint-expression introduced by each type-constraint ([temp.param]) in the declaration's template-parameter-list, in order of appearance, and

the constraint-expression introduced by a requires-clause following a template-parameter-list ([temp.pre]), and

the constraint-expression introduced by each type-constraint in the parameter-type-list of a function declaration, and

the constraint-expression introduced by a trailing requires-clause ([dcl.decl]) of a function declaration ([dcl.fct]).

The formation of the associated constraints establishes the order in which constraints are instantiated when checking for satisfaction ([temp.constr.constr]).

[Example 1: template concept C = true;

template void f1(T);template requires C void f2(T);template void f3(T) requires C;

The functions f1, f2, and f3 have the associated constraint C.

template concept C1 = true;template concept C2 = sizeof(T) > 0;

template void f4(T) requires C2;template requires C1 && C2 void f5(T);

The associated constraints of f4 and f5 are C1 ∧ C2.

template requires C2 void f6();template requires C1 void f7();

The associated constraints off6 are C1 ∧ C2, and those off7 are C2 ∧ C1.

— end example]

4

#

When determining whether a given introducedconstraint-expression C1 of a declaration in an instantiated specialization of a templated class is equivalent ([temp.over.link]) to the correspondingconstraint-expression C2 of a declaration outside the class body,C1 is instantiated.

If the instantiation results in an invalid expression, the constraint-expressions are not equivalent.

[Note 1:

This can happen when determining which member template is specialized by an explicit specialization declaration.

— end note]

[Example 2: template concept C = true;template struct A {template U f(U) requires C; // #1template U f(U) requires C; // #2};

template <> template U A::f(U u) requires C { return u; } // OK, specializes #2

Substituting int for T in C<typename T::type> produces an invalid expression, so the specialization does not match #1.

Substituting int for T in C produces C, which is equivalent to the constraint-expression for the specialization, so it does match #2.

— end example]

13.5.4 Constraint normalization [temp.constr.normal]

1

#

The normal form of an expression E is a constraint that is defined as follows:

  • (1.1)

    The normal form of an expression ( E ) is the normal form of E.

  • (1.2)

    The normal form of an expression E1 || E2 is the disjunction of the normal forms of E1 and E2.

  • (1.3)

    The normal form of an expression E1 && E2 is the conjunction of the normal forms of E1 and E2.

  • (1.4)

    For a concept-id C<A1, A2, …, An> termed CI:

    • (1.4.1)

      If C names a dependent concept, the normal form of CI is a concept-dependent constraint whose concept-id is CI and whose parameter mapping is the identity mapping.

    • (1.4.2)

      Otherwise, to form CE, any non-dependent concept template argument Ai is substituted into the constraint-expression of C. If any such substitution results in an invalid concept-id, the program is ill-formed; no diagnostic is required. The normal form of CI is the result of substituting, in the normal form N of CE, appearances of C's template parameters in the parameter mappings of the atomic constraints in N with their respective arguments from C. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.

    [Example 1: template concept A = T::value || true;template concept B = A<U*>;template concept C = B<V&>; Normalization of B's constraint-expression is valid and results inT::value (with the mapping T↦U*) ∨ true (with an empty mapping), despite the expression T::value being ill-formed for a pointer type T. Normalization of C's constraint-expression results in the program being ill-formed, because it would form the invalid type V&* in the parameter mapping. — end example]

  • (1.5)

    For a fold-operator Op ([expr.prim.fold]) that is either && or ||:

    • (1.5.1)

      The normal form of an expression ( ... Op E ) is the normal form of ( E Op ... ).

    • (1.5.2)

      The normal form of an expression ( E1 Op ... Op E2 ) is the normal form of + (1.5.2.1) ( E1 Op ... ) Op E2 if E1 contains an unexpanded pack, or

      • [(1.5.2.2)](#normal-1.5.2.2)
        

E1 Op ( E2 Op ... ) otherwise.

  • (1.5.3)

    The normal form of an expression F of the form ( E Op ... ) is as follows:
    If E contains an unexpanded concept template parameter pack, it shall not contain an unexpanded template parameter pack of another kind. Let E′ be the normal form of E.

    • [(1.5.3.1)](#normal-1.5.3.1)
      If E contains
      

an unexpanded concept template parameter pack Pk that has corresponding template arguments in the parameter mapping of any atomic constraint (including concept-dependent constraints) of E′, the number of arguments specified for all such Pk shall be the same number N. The normal form of F is the normal form of E0 Op Op EN−1 after substituting in Ei the respective ith concept argument of each Pk. If any such substitution results in an invalid type or expression, the program is ill-formed; no diagnostic is required.

+
      [(1.5.3.2)](#normal-1.5.3.2)
      Otherwise,

the normal form of F is a fold expanded constraint ([temp.constr.fold]) whose constraint is E′ and whose fold-operator is Op.

  • (1.6)

    The normal form of any other expression E is the atomic constraint whose expression is E and whose parameter mapping is the identity mapping.

2

#

The process of obtaining the normal form of aconstraint-expression is callednormalization.

[Note 1:

Normalization of constraint-expressions is performed when determining the associated constraints ([temp.constr.constr]) of a declaration and when evaluating the value of an id-expression that names a concept specialization ([expr.prim.id]).

— end note]

3

#

[Example 2: template concept C1 = sizeof(T) == 1;template concept C2 = C1 && 1 == 2;template concept C3 = requires { typename T::type; };template concept C4 = requires (T x) { ++x; };

template void f1(U); // #1template void f2(U); // #2template void f3(U); // #3

The associated constraints of #1 aresizeof(T) == 1 (with mapping T↦U) ∧ 1 == 2.

The associated constraints of #2 arerequires { typename T::type; } (with mapping T↦U).

The associated constraints of #3 arerequires (T x) { ++x; } (with mapping T↦U).

— end example]

[Example 3: templateconcept C = true;template<typename T, template concept CT>concept CC = CT;

template<typename U, template<typename, template concept> concept CT>void f() requires CT<U*, C>;templatevoid g() requires CC<U*, C>;

The normal form of the associated constraints of f is the concept-dependent constraint CT<T, C>.

The normal form of the associated constraints of g is the atomic constraint true.

— end example]

[Example 4: templateconcept A = true;templateconcept B = A && true; // B subsumes Atemplateconcept C = true;templateconcept D = C && true; // D subsumes Ctemplate<typename T, template concept... CTs>concept all_of = (CTs && ...);

template requires all_of<T, A, C>constexpr int f(T) { return 1; } // #1template requires all_of<T, B, D>constexpr int f(T) { return 2; } // #2static_assert(f(1) == 2); // ok

The normal form of all_of<T, A, C> is the conjunction of the normal forms of A and C.

Similarly, the normal form of all_of<T, B, D> is the conjunction of the normal forms of B and D.

#2 therefore is more constrained than #1.

— end example]

[Example 5: template<typename T, template concept>struct wrapper {};

template<typename... T, template concept... CTs>int f(wrapper<T, CTs>...) requires (CTs && ...); // error: fold expression contains// different kinds of template parameters — end example]

13.5.5 Partial ordering by constraints [temp.constr.order]

1

#

A constraint P subsumes a constraint Q if and only if, for every disjunctive clause Pi in the disjunctive normal form110 of P, Pi subsumes every conjunctive clause Qj in the conjunctive normal form111 of Q, where

a disjunctive clause Pi subsumes a conjunctive clause Qj if and only if there exists an atomic constraint Pia in Pi for which there exists an atomic constraint Qjb in Qj such that Pia subsumes Qjb,

an atomic constraint A subsumes another atomic constraintB if and only if A and B are identical using the rules described in [temp.constr.atomic], and

a fold expanded constraint A subsumes another fold expanded constraint B if they are compatible for subsumption, have the same fold-operator, and the constraint of A subsumes that of B.

[Example 1:

Let A and B be atomic constraints.

The constraint A ∧ B subsumes A, but A does not subsume A ∧ B.

The constraint A subsumes A ∨ B, but A ∨ B does not subsume A.

Also note that every constraint subsumes itself.

— end example]

2

#

[Note 1:

The subsumption relation defines a partial ordering on constraints.

This partial ordering is used to determine

the best viable candidate of non-template functions ([over.match.best]),

the address of a non-template function ([over.over]),

the matching of template template arguments ([temp.arg.template]),

the partial ordering of class template specializations ([temp.spec.partial.order]), and

the partial ordering of function templates ([temp.func.order]).

— end note]

3

#

The associated constraints C of a declaration Dare eligible for subsumption unless C contains a concept-dependent constraint.

4

#

A declaration D1 isat least as constrained as a declaration D2 if

D1 and D2 are both constrained declarations andD1's associated constraints are eligible for subsumption and subsume those of D2; or

D2 has no associated constraints.

5

#

A declaration D1 is more constrained than another declaration D2 when D1 is at least as constrained as D2, and D2 is not at least as constrained as D1.

[Example 2: template concept C1 = requires(T t) { --t; };template concept C2 = C1 && requires(T t) { *t; };

template void f(T); // #1template void f(T); // #2template void g(T); // #3template void g(T); // #4 f(0); // selects #1 f((int*)0); // selects #2 g(true); // selects #3 because C1 is not satisfied g(0); // selects #4 — end example]

[Example 3: template<template concept CT, typename T>struct S {};templateconcept A = true;

template<template concept X, typename T>int f(S<X, T>) requires A { return 42; } // #1template<template concept X, typename T>int f(S<X, T>) requires X { return 43; } // #2 f(S<A, int>{}); // ok, select #1 because #2 is not eligible for subsumption — end example]

6

#

A non-template function F1 is more partial-ordering-constrained than a non-template function F2 if

they have the same non-object-parameter-type-lists ([dcl.fct]), and

if they are member functions, both are direct members of the same class, and

if both are non-static member functions, they have the same types for their object parameters, and

the declaration of F1 is more constrained than the declaration of F2.

110)110)

A constraint is in disjunctive normal form when it is a disjunction of clauses where each clause is a conjunction of fold expanded or atomic constraints.

For atomic constraints A, B, and C, the disjunctive normal form of the constraintA ∧ (B ∨ C) is(A ∧ B) ∨ (A ∧ C).

Its disjunctive clauses are (A ∧ B) and (A ∧ C).

111)111)

A constraint is in conjunctive normal form when it is a conjunction of clauses where each clause is a disjunction of fold expanded or atomic constraints.

For atomic constraints A, B, and C, the constraintA ∧ (B ∨ C) is in conjunctive normal form.

Its conjunctive clauses are A and (B ∨ C).