3

Suppose we have the following:

struct C;
struct B {
  C& c;
  B(C& c) : c{c} {}
};

struct A;
struct C {
  A& a;
  C(A& a) : a{a} {}
};

struct A {
  B b;
  C c;
  A() : b(c), c(*this) {}
};

It seems to be legal to initialize member variable c with this because this can be used to refer to the object being initialized in the initializer list. But when passing c to b's constructor, c's lifetime has not begun and since it does have a reference member, an old pointer cannot be used to manipulate the new C object.

I believe the above is well behaved / intuitively reasonable as b never uses it's c reference before the object c is fully constructed though.

What part of the language specifies that this works appropriately?

Edit: to clarify, what makes it legal to access c through b after construction for the A object has been completed? i.e after all lifetimes have begun.

Lawrence
  • 862
  • 1
  • 7
  • 15
  • Could you just put `C c;` before `B b;`? – François Andrieux Jan 15 '19 at 21:07
  • While the memory for the objects exist and is allocated, when you initialize `b` then `c` have *not* been initialized. As long as you're aware of that, what you're doing in the code you show is fine. – Some programmer dude Jan 15 '19 at 21:08
  • @FrançoisAndrieux suppose for exposition this is not possible – Lawrence Jan 15 '19 at 21:11
  • While the linked (duplicate) question talks about addresses, the provided answer covers glvalues; so both pointers-to and `&` to an object are covered by the answer. – Yakk - Adam Nevraumont Jan 15 '19 at 21:11
  • @Yakk-AdamNevraumont, @Barry the linked question stores a pointer to a trivial type though so that pointer can be legally used to access the value once the `member` object is constructed. I specifically used an object holding a reference to negate that path. – Lawrence Jan 15 '19 at 21:13
  • 1
    I don't see how the linked answer doesn't answer this question. – Barry Jan 15 '19 at 21:36
  • 1
    @Lawrence The type being pointed to has no bearing on the answer to this problem? It is illegal to follow the pointer to the trivial type before the trivial type object's lifetime begins, it is illegal to follow your reference before the object referred to lifetime begins. The same part of the standard covers this issue, and it doesn't care what the thing referred to is. – Yakk - Adam Nevraumont Jan 15 '19 at 21:52
  • @Yakk-AdamNevraumont I agree with both of the statements regarding illegal behaviors. My question is regarding what is legal for access after the object's lifetime does begin. In the trivial case, the reference automatically refers to the new object according to `basic.life#8`. However, this does not cover access in the non-trivial case. – Lawrence Jan 15 '19 at 21:58
  • @lawr you appear to be confusing *reusing* space where an object was before, which works like you say (and link to in the question), and pointers (well glvalues referring to) to objects before their lifetime has begun, which works like the answer your question has been marked as a duplicate of describes. For the motivating difference (not the normative text difference), references behave a lot like const pointer fields (not pointers to const); and the compilers must be free to assume const values are not changes (or, references are not reseated). Whew, that is hard to say right. Make sense? – Yakk - Adam Nevraumont Jan 16 '19 at 00:33
  • @Yakk-AdamNevraumont Perhaps I'm just being thick headed but I don't understand. `b` now stores an old pointer(reference) to the old content of `c` (since the real `c`'s lifetime begins after `b`'s construction ends). Isn't accessing `c` through `b` undefined afterwards? – Lawrence Jan 16 '19 at 05:44
  • @lawr it stores a pointer to the object at c from before c's lifetime begins. There is no "old content" to point at. The linked answer describes how and when this is legal. This is completdly unrelated to reusing storage; the storage of `c` was *never used* by any other object (in the abstract machine). There has never been anything there before. If you had an object in that storage and reused it (`C c; C* pold=&c; c->~C(); C* pnew = ::new((void*)&c) C;`) then accessing the new object through a pointer to the old is illegal; but that isn't what is happening here. – Yakk - Adam Nevraumont Jan 16 '19 at 05:51
  • @Yakk-AdamNevraumont OK I see, that's an interesting distinction then. Because if you have e.g an aligned char buf and then placement new into it, you have to std::launder (old) pointers to the storage before using them. It seems a bit arbitrary to say that the storage being allocated for `c` in this case can directly be used to refer to the object that will be constructed. – Lawrence Jan 16 '19 at 18:07

0 Answers0