[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;
is not syntactically well-formed. You can either have a variable definition with initializer (i.e.int x = x;
, though it is UB) or a xor assignment expression statement (i.e.x ^= x;
, though it is UB ifx
is of typeint
, was default-initialized and not assigned to beforehand). You cannot mix these two into one. – Executor