constexpr
2023-01-08
org: | ISO/IEC JCT1/SC22/WG14 | document: | N3078 | |
target: | IS 9899:2023 | version: | 1 | |
date: | 2023-01-08 | license: | CC BY |
These comments all look legitimate to us. We revise them and show them in their respective context and then propose to poll them together in one go.
Stating that floating values shall be constant expressions ensures they can be used in static initializers, but the stronger condition of being arithmetic constant expressions (rather than some other kind of implementation-defined constant expression permitted in initializers) is needed for them to be usable in
constexpr
initializers.
Assuming those macros should be usable in
constexpr
initializers, change “all floating values shall be constant expressions” to “all floating values shall be arithmetic constant expressions”.
In the context of 5.2.4.2.2 p15
15 All integer values in the
<float.h>
header, exceptFLT_ROUNDS
, shall be constant expressions suitable for use in conditional expression inclusion preprocessing directives; all floating values shall be arithmetic constant expressions. All exceptCR_DECIMAL_DIG
(F.5),DECIMAL_DIG
,DEC_EVAL_METHOD
,FLT_EVAL_METHOD
,FLT_RADIX
, andFLT_ROUNDS
have separate names for all floating types. The floating-point model representation is provided for all values exceptDEC_EVAL_METHOD
,FLT_EVAL_METHOD
andFLT_ROUNDS
.
When should it be possible to initialize an object of structure or union type with a named constant or compound literal constant of such type? The current wording seems to indicate that it is only possible at automatic storage duration – but when such initialization occurs, it is permitted even when the object being initialized is
constexpr
. Since the normal rule is that the requirements onconstexpr
initializers are stricter than those on static storage duration initializers, it is an anomaly to allow named and compound literal constant of structure or union type inconstexpr
initializers of automatic storage duration but not in any initializers of static storage duration.
Assuming such initializers are intended to be allowed at static storage duration, in paragraph 14, remove “that has automatic storage duration”. Add a footnote to the end of the paragraph: “If the object being initialized does not have automatic storage duration, this case violates a constraint unless the expression is a named constant or compound literal constant (6.6).”. Also remove “that has automatic storage duration” in J.2 item 81.
We agree that this has been an oversight. The proposed changes in context look as follows. For 6.7.10 p14
14 The initializer for a structure or union object
that has automatic storage durationshall be either an initializer list as described below, or a single expression that has compatible structure or union type. In the latter case, the initial value of the object, including unnamed members, is that of the expression.FNT)
FNT) If the object being initialized does not have automatic storage duration, this case violates a constraint unless the expression is a named constant or compound literal constant (6.6).
For J.2
( 81) The initializer for a structure or union object
that has automatic storage durationis neither an initializer list nor a single expression that has compatible structure or union type (6.7.10).
The introduction of the keyword
constexpr
provides a new way to declare an identifier with internal linkage at file scope. Since such a new feature should not be immediately considered obsolescent, presumably it should only be the pre-existing case (of redeclaring withoutstatic
after an initial declaration withstatic
) that is listed as obsolescent.
Change “
static
” to “static
orconstexpr
”.
In the context of 6.11.2 this looks as follows.
Declaring an identifier with internal linkage at file scope without the
static
orconstexpr
storage-class specifier is an obsolescent feature.
To ensure usability in constexpr initializers, the
_Complex_I
and_Imaginary_I
macros should be required to expand to arithmetic constant expressions.
Change “constant expression” to “arithmetic constant expression” in paragraphs 5 and 6.
In the context of 7.3.1 p5 and p6 that reads
5 The macro
complex
expands to
_Complex
; the macro
_Complex_I
expands to an arithmetic constant expression of type
float _Complex
, with the value of the imaginary unit.242)
6 The macros
imaginary
and
_Imaginary_I
are defined if and only if the implementation supports imaginary types243); and, if defined, they expand to
_Imaginary
and an arithmetic constant expression of typefloat _Imaginary
with the value of the imaginary unit.
The
CMPLX
macros should expand to arithmetic constant expressions to be usable inconstexpr
initializers, not just those of static or thread storage duration. (But if the implementation has floating expressions that are valid in static initializers but not in constexpr initializers,CMPLX
calls with such arguments should have the same property.)
At the end of the paragraph, add another sentence: “The resulting expression shall be an arithmetic constant expression, provided both arguments are arithmetic constant expressions.”.
Put into the context of 7.3.9.3:
2 The
CMPLX
macros expand to an expression of the specified complex type, with the real part having the (converted) value ofx
and the imaginary part having the (converted) value ofy
. The resulting expression shall be suitable for use as an initializer for an object with static or thread storage duration, provided both arguments are likewise suitable. The resulting expression shall be an arithmetic constant expression, provided both arguments are arithmetic constant expressions.
To be usable in constexpr initializers, constant expressions should be specified as arithmetic constant expressions.
Change “constant expression” to “arithmetic constant expression”, in paragraphs 5, 6, 7, 8, 9 and 10.
In the context of 7.12
5 The macro
HUGE_VAL
expands to a
double
arithmetic constant expression, not necessarily representable as afloat
, whose value is the maximum value returned by library functions when a floating result of typedouble
overflows under the default rounding mode, either maximum finite number in the type or positive or unsigned infinity. The macros
HUGE_VALF HUGE_VALL
are respectively float and long double analogs of HUGE_VAL278) .
6 The macros in this paragraph are only present if the implementation defines
__STDC_IEC_60559_DFP__
and additionally the user code defines__STDC_WANT_IEC_60559_EXT__
before any inclusion of<math.h>
. The macro
HUGE_VAL_D32
expands to an arithmetic constant expression of type
_Decimal32
representing positive infinity. The macros
HUGE_VAL_D64 HUGE_VAL_D128
are respectively
_Decimal64
and_Decimal128
analogs ofHUGE_VAL_D32
.
7 The macro
INFINITY
is defined if and only if the implementation supports an infinity for the type
float
. It expands to an arithmetic constant expression of typefloat
representing positive or unsigned infinity.
8 The macro
DEC_INFINITY
expands to an arithmetic constant expression of type
_Decimal32
representing positive infinity.
9 The macro
NAN
is defined if and only if the implementation supports quiet NaNs for the
float
type. It expands to an arithmetic constant expression of typefloat
representing a quiet NaN.
10 The macro
DEC_NAN
expands to an arithmetic constant expression of type
_Decimal32
representing a quiet NaN.
Suppose a structure or union object is, as the whole or part of a
constexpr
initializer, initialized with a single expression (a named or compound literal constant) of that type (see comment below on 6.7.10 regarding the details of when exactly this is permitted). How exactly should the constraints thatconstexpr
pointer initializers be null pointer constant be applied in this case, if the structure or union contains such a member (and, in the case of the union, the pointer is the active member or part of the active member)? Should it be a constraint violation because a hypothetical expression accessing the pointer member of the constant structure or union would not itself be a null pointer constant? Or should it be permitted because no such hypothetical expression is actually formed in the initialization process and the initializer actually written for the structure or union is a structure or union constant?
Supposing such an initialization should be permitted, replace “the implicit or explicit initializer value” by “any explicit initializer value”. Add a new Example:
struct s { void *p; };
constexpr struct s A = { nullptr };
constexpr struct s B = A;
/* Although the expression A.p is not a null pointer constant, only a null pointer, the only explicit initializer in the initialization of B is A, not A.p, so no constraint is violated by that initialization. */
If (see comment below on 6.7.10) such initializations are only accepted with automatic storage duration (if the proposed change for that comment is rejected), this example will need adjusting accordingly.
We propose a slightly simpler solution of this problem, namely to relax the constraints on the initialization of pointer constexpr
objects.
6.7.1 Storage-class specifiers
Constraints
…
5 … If an object or subobject declared with storage-class specifier constexpr has pointer, integer, or arithmetic type, the implicit or explicit initializer value for it shall be
anullpointer constant140), an integer constant expression, or an arithmetic constant expression, respectively.
140) The named constant or compound literal constant corresponding to an object declared with storage-class specifier constexpr and pointer type is a constant expression with a value null, and thus a null pointer and an address constant. Thus, such a named constant is a valid initializer for other
constexpr
declarations, provided the pointer types match accordingly. However, even if it has typevoid*
it is not a null pointer constant.
…
15’ EXAMPLE 1’ Although in the following the expression
A.p
is not a null pointer constant, only a constant expression with pointer type and value null, the member-wise initialization ofB
withA
is valid.
struct s { void *p; };
constexpr struct s A = { nullptr };
constexpr struct s B = A;
The requirement on
constexpr
initializers that “The value of any constant expressions or of any character in a string literal of the initializer shall be exactly representable in the corresponding target type; no change of value shall be applied” is unclear in many cases, especially concerning floating point, regarding what exactly counts as a change of value when the type changes; the footnote and examples are only of limited value for clarifying this, and to the extent that the do clarify it, it is not clear that the results follow from the normative text. See attached document c2x-constexpr-init.pdf for more details of this issue.
The referred document is N3071
The only values for which there seems to be ambiguity here are different categories of floating point values. This ambiguity stems from an unclear specification of what the term “value” comprises for floating point. This is not inherent to the constexpr
proposal, it only puts the finger on it.
May a negative floating zero be used to initialize an integer?
Are complex values with imaginary part 0
identified with the corresponding real value? (testable by ==
)
Does the quantum exponent take part in the value of a decimal floating point? (not testable by ==
)
Does the payload of a NaN take part in the value? (not testable by ==
)
There are many float
values that round-trip through _Decimal64
to an identical value, but that nevertheless have different mathematical value from the corresponding _Decimal64
. (not testable by ==
within either of the types)
Therefore We refer to the FP SG to resolve this issue.
May a declaration in a
for
loop use theconstexpr
storage class specifier (such an object being considered implicitly auto if no other storage class specifier used), or does “having storage classauto
orregister
” exclude that case? (Cf. DR#277 regarding interpretation of that sentence.)
Change “having storage class
auto
orregister
” to “with automatic storage duration”, to avoid ambiguity (assuming theconstexpr
case should be allowed).
In principle, we agree with the change, but see below for other options. In the context of 6.8.5 this is
The declaration part of a
for
statement shall only declare identifiers for objectshaving storage classwith automatic storage duration.auto
orregister
But this puts to light another oversight, here. Now it would be possible to evaluate compound literals with a different storage duration in the context of such a declaration. We are not even sure about the reasons why other storage durations were not allowed here and if these reasons still apply today.
If we want objects obtained from compound literals to be the handled the same, this would need a change in 6.8.5
3
The declaration part of aThe declaration and expression parts of an iteration statement shall only declare identifiers for objects or evaluate compound literals with automatic storage duration.for
statement shall only declare identifiers for objects having storage classauto
orregister
.
but also one in 6.8.4 where we would add a new constraint section
Constraints
1’ The expression part of a selection statement shall only evaluate compound literals with automatic storage duration.
As said, we are not sure why this constraint exists in the first place. If there is no such reason (anymore) we could also remove it completely.
In the context of 6.8.5 this is
The declaration part of afor
statement shall only declare identifiers for objects having storage classauto
orregister
.
Does WG14 agree with the solution for NB comment GB-125 to remove the constraint completely?
If not, does WG14 agree with the combined solution for NB comment GB-125 and static compound literals?
If not, does WG14 agree with the proposed solution for NB comment GB-125?
It might be appropriate to allow
constexpr
objects with static storage duration to be accessed in signal handlers.
Consider the following change: after “not a lock-free atomic object” insert “and that is not declared with the
constexpr
storage-class specifier”.
Put into context in 7.14.1.1 p5
5 If the signal occurs other than as the result of calling the abort or raise function, the behavior is undefined if the signal handler refers to any object with static or thread storage duration that is not a lock-free atomic object and that is not declared with the
constexpr
storage-class specifier other than by assigning a value to an object declared as volatilesig_atomic_t
, or the signal handler calls any function in the standard library other than
- the
abort
function,- the
_Exit
function,- the
quick_exit
function,- the functions in
<stdatomic.h>
(except where explicitly stated otherwise) when the atomic arguments are lock-free,- the
atomic_is_lock_free
function with any atomic argument, or- the
signal
function with the first argument equal to the signal number corresponding to the signal that caused the invocation of the handler. Furthermore, if such a call to thesignal
function results in aSIG_ERR
return, the object designated byerrno
has an indeterminate representation.308)
N3018 (the constexpr specifier for object definitions) introduces the ability to have constant expressions in more situations, but it does not update the rules for constant expression evaluation of floats. This paragraph is not possible to implement in the case of floating-point types. C++ avoids this with a recommended practice http://eel.is/c++draft/expr.const#13 because they recognized the implementation impossibilities. Consider code like:
constexpr float f = 1.0f;
constexpr float g = 3.0f;
(FE_TOWARDSZERO);
fesetroundconstexpr float h = f / g;
Short of performing a full analysis of the control flow graph for the translation unit, there’s no way to meet this semantic requirement.
Consider using a recommended practice for the floating-point semantic requirements instead of mandating them.
What evaluation rules apply to initializers for objects with automatic storage duration declared with the
constexpr
storage-class specifier? Are the same rules applied as for static and thread storage duration (so allowing such an initializer that might raise exceptions if evaluated at execution time, e.g. “constexpr double x = (double)(1.0 / 3.0);
”? Or are execution-time evaluation rules applied, resulting in a constraint violation because(double)(1.0 / 3.0)
is not considered to evaluate to a constant? And does this differ depending on whether theFENV_ACCESS
pragma is in effect, theFENV_ROUND
orFENV_DEC_ROUND
pragma is in effect, or both?
Make some suitable change (both to normative text, if any such initializers are to be accepted that would raise exceptions at execution time, and adding examples, in any case) to clarify the handling of such initializers, following any recommendations from the C floating-point group.
Regardless of the particular solutins that the FP SG may find for the mentioned problems, this discussion shows that we have missed an important aspect of constexpr
objects, namely the question if their initializers are evaluated at translation time or at each time the defining construct is met.
(JG: I would have preferred to have static storage duration for all these beasts, that would have avoided a lot of trouble. The only reason we have that is compatibility with C++)
In the context of 6.2.4 p6
6 For such an object that does not have a variable length array type, its lifetime extends from entry into the block with which it is associated until execution of that block ends in any way. (Entering an enclosed block or calling a function suspends, but does not end, execution of the current block.) If the block is entered recursively, a new instance of the object is created each time. The initial representation of the object is indeterminate. If an initialization is specified for the object and it is not specified with
constexpr
, it is performed each time the declaration or compound literal is reached in the execution of the block; if it is specified withconstexpr
the initializer is evaluated once at translation time and the new instance of the object is initialized to that fixed value each time the specification is reached; otherwise, the representation of the object becomes indeterminate each time the declaration is reached.
This discussion also sheds light on a similar problem for objects with thread storage duration that might have been dormant since their introduction in C11. These also have initializers that obey the same rules as for static, but it is not clear if they are evaluated each time a thread is started. (Hopefully nobody does that.) C17 only deals with this problem in optional floating point Annex F, not in general. So this may also need some correction in the context of 6.2.4 p4 such that implementations that implement threads but not Annex F (such as clang) are bound to these semantics.
4 An object whose identifier is declared with the storage-class specifier
thread_local
has thread storage duration. Its explicit or implicit initializer is evaluated prior to program execution, its lifetime is the entire execution of the thread for which it is created, and its stored value is initialized with the previously determined value when the thread is started. There is a distinct object per thread, and use of the declared name in an expression refers to the object associated with the thread evaluating the expression. The result of attempting to indirectly access an object with thread storage duration from a thread other than the one with which the object is associated is implementation-defined.
Does WG14 want to apply the proposed resolution to US-075 and GB-279 for automatic storage duration?
Does WG14 want to apply the proposed resolution also for thread storage duration?