10

This is similar in spirit to a question that was asked and answered for c. The comments there implied that a precise answer would be different for c++, so here is a similar question for code written in c++.

Is the following program well defined?

int f(int& b) 
{ 
  b = 42; 
  return b; 
}

int a { f(a) };

It seems all right to me, but on the other hand, how is a being constructed from a value that is computed by a function, that itself modifies a? I'm having a chicken-and-egg feeling about this, so an explanation would be nice. For what it's worth, it appears to work.

This seems to be the same question, so here goes; would the answer be different for class types and fundamental types. i.e. Is the following well formed?

struct S { int i; };

S f(S& b) 
{ 
    b.i = 42; 
    return b; 
}

S a { f(a) };

Again, for what it's worth, this appears to work as well.

cigien
  • 57,834
  • 11
  • 73
  • 112
  • I don’t know for sure, hence the comment instead of answer, but my guess would be either that it’s always UB or that it’s well-defined only for fundamental types. – Daniel H Apr 15 '20 at 01:22
  • Yes, it's legal. There's such question on SO. Let's search it. – 273K Apr 15 '20 at 01:28
  • @DanielH ok, in that case, I've edited the question to be more precise. – cigien Apr 15 '20 at 01:29
  • @S.M. That would be great. I couldn't find it though. – cigien Apr 15 '20 at 01:32
  • Found it https://stackoverflow.com/a/981811/6752050 – 273K Apr 15 '20 at 01:32
  • 3
    I recall reading this same question a few months ago ; from memory it used to be legal but no longer is as of C++20 – M.M Apr 15 '20 at 01:33
  • @S.M. That's a different question. In this one they are doing an assignment in the function before the initialization is final – NathanOliver Apr 15 '20 at 01:33
  • @S.M. Is that the same as my example? – cigien Apr 15 '20 at 01:33
  • It is very similar. Copy constructor from there plays the role of your f. – 273K Apr 15 '20 at 01:35
  • You can declare the function as more brain breaker `S& f(S& b)` – 273K Apr 15 '20 at 01:37
  • unconstructed (new)-> uninitialized (init-or-assigned)-> initialized-and-valid (move)-> valid-but-unspecified (delete)-> destructed ... these are not standardese terminologies. – Eljay Apr 15 '20 at 01:39

1 Answers1

8

The behaviour seems to be undefined in C++20. The change was made by P1358, resolving CWG 2256. As defect resolutions are generally retroactive, this code should be considered UB in all versions of C++.

According to [basic.life]/1:

... The lifetime of an object of type T begins when:

  • storage with the proper alignment and size for type T is obtained, and
  • its initialization (if any) is complete (including vacuous initialization) ...

At the time when f(a) is called, the object a has not yet begun its lifetime since its initialization has not completed. According to [basic.life]/7:

Similarly, before the lifetime of an object has started but after the storage which the object will occupy has been allocated ... any glvalue that refers to the original object may be used but only in limited ways. ... The program has undefined behavior if:

  • the glvalue is used to access the object ...

Thus, writing to a before its initialization has completed is UB, even though the storage has already been allocated.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312
  • So, just to clarify, there's no difference between class types, and fundamental types here? – cigien Apr 15 '20 at 01:44
  • Unrelated to the question, how can there be an "original object" if the lifetime of the object is not started? – xskxzr Apr 15 '20 at 01:46
  • @cigien • primitive fundamental types are objects just as class types are objects. No difference regarding object life cycle. – Eljay Apr 15 '20 at 01:53
  • @Eljay ok, makes sense. After this question was answered in the negative, I'm not so sure about anything anymore :p – cigien Apr 15 '20 at 01:55
  • @cigien For objects of class type, the rules are a bit more relaxed. While the constructor is running, the lifetime has not started yet, but you are still allowed to access the object in some ways (e.g. you can access members whose mem-initializers have already completed). However, this exception does not apply to your `S` example, so it would also be UB. – Brian Bi Apr 15 '20 at 02:16
  • @Brian Thanks for the explanation. I'll have to look into this a bit more to wrap my head around it. – cigien Apr 15 '20 at 03:02