Use of variable in own initializer
Asked Answered
E

1

23

[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:

Executor answered 22/12, 2019 at 7:54 Comment(8)
@LanguageLawyer I am not confident that I am correct, especially not if noone has answered yet. If others are going to agree with me here, I might file one later (or maybe someone else will before me), but I don't want to file issues I am not sure about.Executor
@LanguageLawyer: It can’t be an editorial issue if the working paper unambiguously says the wrong thing.Wornout
@DavisHerring Uhm. Why is it unambiguously wrong?Coppage
@LanguageLawyer: I could be wrong on either count, but the interpretation in the question seems correct (so “unambiguously”), yet it doesn’t seem likely that the committee intended to make these constructions undefined in C++20 (so “wrong”).Wornout
The word is changed by P1358.Katlaps
@Katlaps Right, and in the mean time LanguageLawyer also filed an editorial issue, which seems to have been forwarded to CWG for clarification of intention.Executor
I believe int x ^= x; is not undefiend, am I wrong?Water
@Water 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 if x is of type int, was default-initialized and not assigned to beforehand). You cannot mix these two into one.Executor
S
9

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.

Statis answered 27/12, 2019 at 4:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.