constexpr2023-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
constexprinitializers.
Assuming those macros should be usable in
constexprinitializers, 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_ROUNDShave separate names for all floating types. The floating-point model representation is provided for all values exceptDEC_EVAL_METHOD,FLT_EVAL_METHODandFLT_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 onconstexprinitializers 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 inconstexprinitializers 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
constexprprovides 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 withoutstaticafter an initial declaration withstatic) that is listed as obsolescent.
Change “
static” to “staticorconstexpr”.
In the context of 6.11.2 this looks as follows.
Declaring an identifier with internal linkage at file scope without the
staticorconstexprstorage-class specifier is an obsolescent feature.
To ensure usability in constexpr initializers, the
_Complex_Iand_Imaginary_Imacros 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
complexexpands to
_Complex; the macro
_Complex_Iexpands to an arithmetic constant expression of type
float _Complex, with the value of the imaginary unit.242)
6 The macros
imaginaryand
_Imaginary_Iare defined if and only if the implementation supports imaginary types243); and, if defined, they expand to
_Imaginaryand an arithmetic constant expression of typefloat _Imaginarywith the value of the imaginary unit.
The
CMPLXmacros should expand to arithmetic constant expressions to be usable inconstexprinitializers, 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,CMPLXcalls 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
CMPLXmacros expand to an expression of the specified complex type, with the real part having the (converted) value ofxand 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_VALexpands to a
doublearithmetic constant expression, not necessarily representable as afloat, whose value is the maximum value returned by library functions when a floating result of typedoubleoverflows under the default rounding mode, either maximum finite number in the type or positive or unsigned infinity. The macros
HUGE_VALF
HUGE_VALLare 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_D32expands to an arithmetic constant expression of type
_Decimal32representing positive infinity. The macros
HUGE_VAL_D64
HUGE_VAL_D128are respectively
_Decimal64and_Decimal128analogs ofHUGE_VAL_D32.
7 The macro
INFINITYis defined if and only if the implementation supports an infinity for the type
float. It expands to an arithmetic constant expression of typefloatrepresenting positive or unsigned infinity.
8 The macro
DEC_INFINITYexpands to an arithmetic constant expression of type
_Decimal32representing positive infinity.
9 The macro
NANis defined if and only if the implementation supports quiet NaNs for the
floattype. It expands to an arithmetic constant expression of typefloatrepresenting a quiet NaN.
10 The macro
DEC_NANexpands to an arithmetic constant expression of type
_Decimal32representing a quiet NaN.
Suppose a structure or union object is, as the whole or part of a
constexprinitializer, 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 thatconstexprpointer 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
constexprdeclarations, 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.pis not a null pointer constant, only a constant expression with pointer type and value null, the member-wise initialization ofBwithAis valid.
struct s { void *p; };
constexpr struct s A = { nullptr };
constexpr struct s B = A;The requirement on
constexprinitializers 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
forloop use theconstexprstorage class specifier (such an object being considered implicitly auto if no other storage class specifier used), or does “having storage classautoorregister” exclude that case? (Cf. DR#277 regarding interpretation of that sentence.)
Change “having storage class
autoorregister” to “with automatic storage duration”, to avoid ambiguity (assuming theconstexprcase 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
forstatement shall only declare identifiers for objectshaving storage classwith automatic storage duration.autoorregister
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.forstatement shall only declare identifiers for objects having storage classautoorregister.
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 aforstatement shall only declare identifiers for objects having storage classautoorregister.
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
constexprobjects 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
constexprstorage-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
constexprstorage-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
abortfunction,- the
_Exitfunction,- the
quick_exitfunction,- the functions in
<stdatomic.h>(except where explicitly stated otherwise) when the atomic arguments are lock-free,- the
atomic_is_lock_freefunction with any atomic argument, or- the
signalfunction 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 thesignalfunction results in aSIG_ERRreturn, the object designated byerrnohas 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;
fesetround(FE_TOWARDSZERO);
constexpr 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
constexprstorage-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_ACCESSpragma is in effect, theFENV_ROUNDorFENV_DEC_ROUNDpragma 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 withconstexprthe 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_localhas 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?