Date: Wed, 15 Jan 2025 03:00:42 +0100
According to the standard text and some discussion
got with gcc guys on bugzilla,
1) the constraints associated to a templated entity are
the ones coming from the normal form, either of the single
expression present ([temp.constr.decl] - p(3.1)) or of the
logical AND of the present
expressions ([temp.constr.decl] - p(3.3)) (e.g., type
constraints and requires clauses)
2) the normal form is defined in [temp.constr.normal], with
logical AND, logical OR, fold expressions,
or concept-id instantiation (otherwise, a simple constraint-expression).
3) the normal form will finally be done by atomic constraints,
combined with OR and AND.
4) about requires-clauses, they may have multiple parts in a
sequence (simple requirements, type-requirements, compound
requirements, nested requirements -> see [expr.prim.req]),
and such list is considered in lexical order according to
[expr.prim.req.general] - p5 that reads:
" ... 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. ..."
As interesting detail, that p5 starts with:
"The substitution of template arguments into a
requires-expression can result in the formation
of invalid types or expressions in the immediate
context of its requirements or the violation
of the semantic constraints of those requirements.
In such cases, the requires-expression evaluates to
false; it does not cause the program to be ill-formed. ..."
It's also interesting to note that the words
'in the immediate context of'
were absent in C++20 standard, and
integrated in C++23 one.
5) similar statements are provided when discussing
atomic constraints, from [temp.constr.atomic] - p3:
"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, the constraint is
not satisfied. Otherwise, ... and E shall be a
constant expression of type bool. The constraint
is satisfied if and only if evaluation of E results in true. "
It's also interesting to note that the words
'in the immediate context of the atomic constraint'
were absent in C++20 and C++23 standards, and
integrated in current C++26 draft.
6) [temp.constr.op] - p2 and p3 report the rules about
checking operands of AND and OR one by one in
order, similarly to the lexical order highlighted in first
part of bullet 4) above.
So, both 4) and 5)+6) clearly state that an invalid type/expression
is allowed in immediate contexts, leading to constraint failure,
but they also state that an eventual invalid type/expression
in non-immediate context (or other errors as non-bool
expression for atomic constraint) leading to ill-formed program
(e.g., for lambda expression, as in [temp.deduct.general]-p9),
are effective only at the moment when the satisfaction of
corresponding constraint is verified, which does not happen in
case previous constraints in the same normal form already
determined the true or false evaluation of the overall normal form.
As additional comment, text from point 4) discuss also
the case of always-invalid type/expression for
requirements-clauses, leading to IFNDR,
whereas 5)+6) do not mention such a case for an atomic
constraint.
This apparent lack is recovered in [temp.res.general]-p(6.4)
that reads:
"The program is ill-formed, no diagnostic required, if:
...
any constraint-expression in the program, introduced or otherwise,
has (in its normal form) an atomic constraint A where no satisfaction
check of A could be well-formed and no satisfaction check of A is
performed,"
I have two doubts about staff above, related to different rules
expressed for atomic constraints and requires-clause.
It is ok that requires-clause may exist even outside a
constraint-expression, for example in an if constexpr.
However, when an atomic constraint IS a requires-clause,
how to apply the rule for invalid type/expression in
a non-immediate context when the requires-claus contains
multiple requiremets ?
I mean, let us suppose the requires-clause contains
two requirements and the invalid is in second requirement,
with the first requirement leading to clause false evaluation.
The text from [temp.constr.atomic] - p3, seems to ignore any
lexical order, whereas the text from [expr.prim.req.general] - p5
uses the lexical order and allows not to take care of the
ill-forming on second requirement if first requirement is not
satisfied. The general rule for atomic constraint appears
less relaxed. Is it really applied, dropping the relaxation of
the rule for requires-clause ?
Second doubt: conversevely, when the invalid
always happens, regardless of the substituted template
parameter, the text from [expr.prim.req.general] - p5
reads that this condition is IFNDR, regardless of whether
the invalid is in immediate context or non-immediate
context, whereas the text in [temp.res.general]-p(6.4) does
not lead to IFNDR in case the always-invalid type/expression
is checked at least once (and it is in immediate-context,
allowing the compilation not to fail).
Indeed, having been checked it at least once, the rule
of [temp.res.general]-p(6.4) does not apply (despite the one
for requires-clause still applies).
However, was this 'more-relaxed' general rule for atomic
constraint the actual intention ?
Final (third) doubt is about [temp.constr.normal], where the example
in p1.4) reads that the atomic constraint-expression 'true'
has empty mapping. The point is that no rule
from [temp.constr.normal]
exactly states the possibility to get an empty mapping.
We have part p(1.9) as default case always indicating
an identity mapping. I guess it should be extended to
indicate empty mapping when the expression is constant,
that is, not involving any template parameter (you know
for sure the best formal words to say it there).
I don't believe it's fair to consider the definition of this
special case just through the example in p(1.4).
Thank you.
got with gcc guys on bugzilla,
1) the constraints associated to a templated entity are
the ones coming from the normal form, either of the single
expression present ([temp.constr.decl] - p(3.1)) or of the
logical AND of the present
expressions ([temp.constr.decl] - p(3.3)) (e.g., type
constraints and requires clauses)
2) the normal form is defined in [temp.constr.normal], with
logical AND, logical OR, fold expressions,
or concept-id instantiation (otherwise, a simple constraint-expression).
3) the normal form will finally be done by atomic constraints,
combined with OR and AND.
4) about requires-clauses, they may have multiple parts in a
sequence (simple requirements, type-requirements, compound
requirements, nested requirements -> see [expr.prim.req]),
and such list is considered in lexical order according to
[expr.prim.req.general] - p5 that reads:
" ... 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. ..."
As interesting detail, that p5 starts with:
"The substitution of template arguments into a
requires-expression can result in the formation
of invalid types or expressions in the immediate
context of its requirements or the violation
of the semantic constraints of those requirements.
In such cases, the requires-expression evaluates to
false; it does not cause the program to be ill-formed. ..."
It's also interesting to note that the words
'in the immediate context of'
were absent in C++20 standard, and
integrated in C++23 one.
5) similar statements are provided when discussing
atomic constraints, from [temp.constr.atomic] - p3:
"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, the constraint is
not satisfied. Otherwise, ... and E shall be a
constant expression of type bool. The constraint
is satisfied if and only if evaluation of E results in true. "
It's also interesting to note that the words
'in the immediate context of the atomic constraint'
were absent in C++20 and C++23 standards, and
integrated in current C++26 draft.
6) [temp.constr.op] - p2 and p3 report the rules about
checking operands of AND and OR one by one in
order, similarly to the lexical order highlighted in first
part of bullet 4) above.
So, both 4) and 5)+6) clearly state that an invalid type/expression
is allowed in immediate contexts, leading to constraint failure,
but they also state that an eventual invalid type/expression
in non-immediate context (or other errors as non-bool
expression for atomic constraint) leading to ill-formed program
(e.g., for lambda expression, as in [temp.deduct.general]-p9),
are effective only at the moment when the satisfaction of
corresponding constraint is verified, which does not happen in
case previous constraints in the same normal form already
determined the true or false evaluation of the overall normal form.
As additional comment, text from point 4) discuss also
the case of always-invalid type/expression for
requirements-clauses, leading to IFNDR,
whereas 5)+6) do not mention such a case for an atomic
constraint.
This apparent lack is recovered in [temp.res.general]-p(6.4)
that reads:
"The program is ill-formed, no diagnostic required, if:
...
any constraint-expression in the program, introduced or otherwise,
has (in its normal form) an atomic constraint A where no satisfaction
check of A could be well-formed and no satisfaction check of A is
performed,"
I have two doubts about staff above, related to different rules
expressed for atomic constraints and requires-clause.
It is ok that requires-clause may exist even outside a
constraint-expression, for example in an if constexpr.
However, when an atomic constraint IS a requires-clause,
how to apply the rule for invalid type/expression in
a non-immediate context when the requires-claus contains
multiple requiremets ?
I mean, let us suppose the requires-clause contains
two requirements and the invalid is in second requirement,
with the first requirement leading to clause false evaluation.
The text from [temp.constr.atomic] - p3, seems to ignore any
lexical order, whereas the text from [expr.prim.req.general] - p5
uses the lexical order and allows not to take care of the
ill-forming on second requirement if first requirement is not
satisfied. The general rule for atomic constraint appears
less relaxed. Is it really applied, dropping the relaxation of
the rule for requires-clause ?
Second doubt: conversevely, when the invalid
always happens, regardless of the substituted template
parameter, the text from [expr.prim.req.general] - p5
reads that this condition is IFNDR, regardless of whether
the invalid is in immediate context or non-immediate
context, whereas the text in [temp.res.general]-p(6.4) does
not lead to IFNDR in case the always-invalid type/expression
is checked at least once (and it is in immediate-context,
allowing the compilation not to fail).
Indeed, having been checked it at least once, the rule
of [temp.res.general]-p(6.4) does not apply (despite the one
for requires-clause still applies).
However, was this 'more-relaxed' general rule for atomic
constraint the actual intention ?
Final (third) doubt is about [temp.constr.normal], where the example
in p1.4) reads that the atomic constraint-expression 'true'
has empty mapping. The point is that no rule
from [temp.constr.normal]
exactly states the possibility to get an empty mapping.
We have part p(1.9) as default case always indicating
an identity mapping. I guess it should be extended to
indicate empty mapping when the expression is constant,
that is, not involving any template parameter (you know
for sure the best formal words to say it there).
I don't believe it's fair to consider the definition of this
special case just through the example in p(1.4).
Thank you.
Received on 2025-01-15 02:00:57