Search code examples
c++language-lawyerplacement-new

"Transparent replacement" of baseclass subobject when complete object is replaced?


Please consider the following code snippet:

struct A { int n; };
struct B : A {};
B foo;
new (&foo) B { {42} }; // the new B transparently-replaces foo
int i = foo.n; // is this valid? Does this correctly refer to the new B's A.n ?

According to [basic.life] p8, the new B should transparently replace foo. However, the new B's A-subobject should not transparently replace foo.A because it is a "potentially-overlapping subobject", being a base class subobject.

However, is it enough for the enclosing B to be replaced for this to be valid?

I have two possible interpretations:

[basic.life] p8 says that

[...] the name of the original object will automatically refer to the new object [...]

thus, foo now refers to the new B.

[expr.ref] p6.2 says the following about the .-member access expression:

If E2 is a non-static data member [...] the expression [E1.E2] designates the corresponding member subobject of the object designated by the first expression.

Therefore foo.n should refer to the new foo's n.

The other interpretation is that foo.n is itself, as a whole, considered the "name of the original object" as in the wording (see above) of [basic.life], and it therefore does not refer to the new int because that int itself is not transparently-replaceable, because its encompassing object, the A, is not transparently-replaceable.

Which of these interpretations might be correct, if any?


Please keep in mind that this is a question. The question is not so much about whether this practically works, but about the "letter of the law" of the standard.


Solution

  • The first interpretation is correct. In the example:

    B foo;
    new (&foo) B { {42} };
    

    B foo is transparently replaceable by the new object of type B created in its storage, since all requirements in [basic.life] p8 are met. Therefore, the name of the object foo now refers to the new object, and:

    int i = foo.n; // this is now valid, and foo.n refers to the subobject in
                   // the new object also named foo
    

    Why the second interpretation is invalid

    foo.n can't be considered a name; it is an expression combining multiple names. I don't believe "name" is formally defined by the standard, but the word is strongly associated with identifier, as can be seen in [lex.name]. The way it's used in [dcl.pre] p4 suggests that at most x, x::y, or ...x::y could be considred names.

    Regardless of what a name is, n (i.e. A::n) is not an object, it is a non-static data member. You can also write foo.A::n which makes this more obvious, and is equivalent. [expr.ref] p6.2 defines E1.E2 for an object expression E1 and a non-static data member E2.

    Conclusion

    The transparent replacement is valid. A::n is not transparently replaceable, but this is okay, because you replace B as whole, and then look up A::n within the replaced B.

    The subobjects of of foo don't have to be individually transparently-replaceable for this to be possible.