To determine if an atomic constraint is
satisfied,
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
. 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<typename T> concept C =
sizeof(T) == 4 && !true;
template<typename T> struct S {
constexpr operator bool() const { return true; }
};
template<typename T> requires (S<T>{})
void f(T);
void f(int);
void g() {
f(0);
}
—
end example]
To determine if
CD is
satisfied,
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
. [
Note 1:
Normalization of
CI can be ill-formed with no diagnostic required
. —
end note]
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.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:
template<typename>
concept C = true;
template<typename T, template<typename> concept CC>
concept D = CC<T>;
template<typename U,
template<typename> concept CT,
template<typename, template<typename> 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 mapping
U↦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<T> with the mapping
T↦U,CC↦CT. By recursion,
C is substituted into
CC<T>, and the result
is normalized to the atomic constraint
true, which is satisfied
. —
end example]
Let
N be the number of elements
in the pack expansion parameters (
[temp.variadic])
. 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
.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
.[
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]
This allows the specification of constraints for that declaration as
an expression:
A declaration's
associated constraints are defined as follows:
Otherwise, the associated constraints are the normal form of a
logical
and expression whose operands are in the
following order:
The formation of the associated constraints
establishes the order in which constraints are instantiated when checking
for satisfaction (
[temp.constr.constr])
. [
Example 1:
template<typename T> concept C = true;
template<C T> void f1(T);
template<typename T> requires C<T> void f2(T);
template<typename T> void f3(T) requires C<T>;
The functions
f1,
f2, and
f3 have the associated
constraint
C<T>.template<typename T> concept C1 = true;
template<typename T> concept C2 = sizeof(T) > 0;
template<C1 T> void f4(T) requires C2<T>;
template<typename T> requires C1<T> && C2<T> void f5(T);
The associated constraints of
f4 and
f5
are
C1<T> ∧ C2<T>. template<C1 T> requires C2<T> void f6();
template<C2 T> requires C1<T> void f7();
The associated constraints of
f6 are
C1<T> ∧ C2<T>,
and those of
f7 are
C2<T> ∧ C1<T>. —
end example]
[
Note 1:
This can happen when determining which member template is specialized
by an explicit specialization declaration
. —
end note]
[
Example 2:
template <class T> concept C = true;
template <class T> struct A {
template <class U> U f(U) requires C<typename T::type>;
template <class U> U f(U) requires C<T>;
};
template <> template <class U>
U A<int>::f(U u) requires C<int> { return u; }
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<T> produces
C<int>,
which is equivalent to the
constraint-expression for the specialization,
so it does match #2
. —
end example]
The
normal form of an
expression E is
a
constraint that 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
disjunction of
the normal forms of
E1 and
E2.The normal form of an expression
E1 && E2
is the conjunction of
the normal forms of
E1 and
E2.For a concept-id
C<A1, A2, …, An> termed CI:
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
.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<typename T> concept A = T::value || true;
template<typename U> concept B = A<U*>;
template<typename V> concept C = B<V&>;
Normalization of
B's
constraint-expression
is valid and results in
T::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]
For a
fold-operator Op (
[expr.prim.fold])
that is either
&& or
||:
The normal form of an expression
( .. Op E )
is the normal form of
( E Op .. ).The normal form of an expression
( E1 Op .. Op E2 )
is the normal form of
- ( E1 Op .. ) Op E2 if E1 contains an unexpanded pack, or
- E1 Op ( E2 Op .. ) otherwise.
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. 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
.The normal form of any other expression
E is
the atomic constraint
whose expression is
E and
whose parameter mapping is the identity mapping
.
[
Example 2:
template<typename T> concept C1 = sizeof(T) == 1;
template<typename T> concept C2 = C1<T> && 1 == 2;
template<typename T> concept C3 = requires { typename T::type; };
template<typename T> concept C4 = requires (T x) { ++x; };
template<C2 U> void f1(U);
template<C3 U> void f2(U);
template<C4 U> void f3(U);
The associated constraints of #1 are
sizeof(T) == 1 (with mapping
T↦U) ∧ 1 == 2.
The associated constraints of #2 are
requires { typename T::type; } (with mapping
T↦U).
The associated constraints of #3 are
requires (T x) { ++x; } (with mapping
T↦U). —
end example]
[
Example 3:
template<typename T>
concept C = true;
template<typename T, template<typename> concept CT>
concept CC = CT<T>;
template<typename U,
template<typename, template<typename> concept> concept CT>
void f() requires CT<U*, C>;
template<typename U>
void 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:
template<typename T>
concept A = true;
template<typename T>
concept B = A<T> && true;
template<typename T>
concept C = true;
template<typename T>
concept D = C<T> && true;
template<typename T, template<typename> concept. CTs>
concept all_of = (CTs<T> && ..);
template<typename T> requires all_of<T, A, C>
constexpr int f(T) { return 1; }
template<typename T> requires all_of<T, B, D>
constexpr int f(T) { return 2; }
static_assert(f(1) == 2);
The normal form of
all_of<T, A, C> is
the conjunction of the normal forms of
A<T> and
C<T>.
Similarly, the normal form of
all_of<T, B, D> is
the conjunction of the normal forms of
B<T> and
D<T>. #2 therefore is more constrained than #1
. —
end example]
[
Example 5:
template<typename T, template<typename> concept>
struct wrapper {};
template<typename. T, template<typename> concept. CTs>
int f(wrapper<T, CTs>.) requires (CTs<T> && ..);
—
end example]
A constraint
P subsumes a constraint
Q
if and only if,
for every disjunctive clause
Pi
in the disjunctive normal form
of P, Pi subsumes every conjunctive clause Qj
in the conjunctive normal form
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 constraint
B 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:
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]
[
Note 1:
The subsumption relation defines a partial ordering on constraints
. This partial ordering is used to determine
—
end note]
The associated constraints
C of a declaration
D
are
eligible for subsumption
unless
C contains a concept-dependent constraint
.A declaration
D1 is
at least as constrained as
a declaration
D2 if
- D1 and D2 are both constrained declarations and
D1's associated constraints
are eligible for subsumption and subsume those of D2; or
- D2 has no associated constraints.
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<typename T> concept C1 = requires(T t) { --t; };
template<typename T> concept C2 = C1<T> && requires(T t) { *t; };
template<C1 T> void f(T);
template<C2 T> void f(T);
template<typename T> void g(T);
template<C1 T> void g(T);
f(0);
f((int*)0);
g(true);
g(0);
—
end example]
[
Example 3:
template<template<typename T> concept CT, typename T>
struct S {};
template<typename T>
concept A = true;
template<template<typename T> concept X, typename T>
int f(S<X, T>) requires A<T> { return 42; }
template<template<typename T> concept X, typename T>
int f(S<X, T>) requires X<T> { return 43; }
f(S<A, int>{});
—
end example]
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.