[basic.scope.pdecl]/1 of the C++20 standard draft had the following (non-normative) example in a note (partial quote from before the merge of pull request 3580, see answer to this question):
unsigned char x = x;
[...] x is initialized with its own (indeterminate) value.
Does this actually have well-defined behavior in C++20?
Generally the self-initialization of the form T x = x;
has undefined behavior by virtue of x
's value being indeterminate before initialization is completed. Evaluating indeterminate values generally causes undefined behavior ([basic.indent]/2), but there is a specific exception in [basic.indent]/2.3 that allows directly initializing an unsigned char
variable from an lvalue unsigned char
with indeterminate value (causing initialization with an indeterminate value).
This alone does therefore not cause undefined behavior, but would for other types T
that are not unsigned narrow character types or std::byte
, e.g. int x = x;
. These considerations applied in C++17 and before as well, see also linked questions at the bottom.
However, even for unsigned char x = x;
, the current draft's [basic.lifetime]/7 says:
Similarly, before the lifetime of an object has started [...] using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
the glvalue is used to access the object, or
[...]
This seems to imply that x
's value in the example can only be used during its lifetime.
[basic.lifetime]/1 says:
[...]
The lifetime of an object of type T begins when:
- [...] and
- its initialization (if any) is complete (including vacuous initialization) ([dcl.init]),
[...]
Thus x
's lifetime begins only after initialization is completed. But in the quoted example x
's value is used before x
's initialization is complete. Therefore the use has undefined behavior.
Is my analysis correct and if it is, does it affect similar cases of use-before-initialization such as
int x = (x = 1);
which, as far as I can tell, were well-defined in C++17 and before as well?
Note that in C++17 (final draft) the second requirement for lifetime to begin was different:
- if the object has non-vacuous initialization, its initialization is complete,
Since x
would have vacuous initialization by C++17's definition (but not the one in the current draft), its lifetime would have already begun when it is accessed in the initializer in the examples given above and so in both examples there was no undefined behavior due to lifetime of x
in C++17.
The wording before C++17 is again different, but with the same result.
The question is not about undefined behavior when using indeterminate values, which was covered in e.g. the following questions:
int x = x;
UB?Notice that a variable that is not initialized does not have a defined value, hence it cannot be used until it is assigned such a value.
Variable initialization in C++ There are two ways to initialize the variable. One is static initialization in which the variable is assigned a value in the program and another is dynamic initialization in which the variables is assigned a value at the run time.
This was opened as an editorial issue. It was forwarded to CWG for (internal) discussion. Approximately 24 hours later, the person who forwarded the issue created a pull request which modifies the example to make it clear that this is UB:
Here, the initialization of the second \tcode{x} has undefined behavior, because the initializer accesses the second \tcode{x} outside its lifetime\iref{basic.life}.
That PR has since been added and the issue closed. So it seems clear that the obvious interpretation (UB due to accessing an object whose lifetime has not started) is the intended interpretation. It appears that the intent of the committee is to make these constructs non-functional, and the standard's non-normative text has been updated to reflect this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With