Is there a (semantic) difference between the return value of placement new and the casted value of its operand?
struct Foo { ... };
char buffer[...];
Foo *a
Access through a
is legal while b
is not. From [basic.compound]
Two objects
a
andb
are pointer-interconvertible if:
they are the same object, or
one is a standard-layout union object and the other is a non-static data member of that object, or
one is a standard-layout class object and the other is the first non-static data member of that object, or, if the object has no non-static data members, the first base class subobject of that object ([class.mem]), or
there exists an object
c
such thata
andc
are pointer-interconvertible, andc
andb
are pointer-interconvertible.If two objects are pointer-interconvertible, then they have the same address, and it is possible to obtain a pointer to one from a pointer to the other via a
reinterpret_cast
. [ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]
They are not the same object, not unions and not subobjects to each other, therefore not pointer-interconvertible.
Note [expr.reinterpret.cast] only guarantees reinterpret_cast<char*>(b) == buffer
.
An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue
v
of object pointer type is converted to the object pointer type “pointer tocv T
”, the result isstatic_cast<cv T*>(static_cast<cv void*>(v))
. [ Note: Converting a prvalue of type “pointer toT1
” to the type “pointer toT2
” (whereT1
andT2
are object types and where the alignment requirements ofT2
are no stricter than those ofT1
) and back to its original type yields the original pointer value. — end note ]
Only a
can safely be used to directly access the Foo
object created by the placement new-expression (which we'll call x
for ease of reference). Using b
requires std::launder
.
The value of a
is specified in [expr.new]/1:
If the entity is a non-array object, the result of the new-expression is a pointer to the object created.
The value of a
is therefore "pointer to x
". This pointer, of course, can safely be used to access x
.
reinterpret_cast<Foo*>(buffer)
applies the array-to-pointer conversion to buffer
(see [expr.reinterpret.cast]/1). The resulting value after the conversion is applied is "pointer to the first element of buffer
".
This is a reinterpret_cast
of an object pointer to an object pointer of a different type, and is defined as equivalent to static_cast<Foo*>(static_cast<void*>(buffer))
by [expr.reinterpret.cast]/7.
The inner cast to void*
is actually an implicit conversion. Per [conv.ptr]/2,
The pointer value is unchanged by this conversion.
Therefore the inner cast yields a void*
with the value "pointer to the first element of buffer
".
The outer cast is governed by [expr.static.cast]/13, which I've lightly reformatted into bullet points:
A prvalue of type “pointer to cv1
void
” can be converted to a prvalue of type “pointer to cv2T
”, whereT
is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1.
If the original pointer value represents the address
A
of a byte in memory andA
does not satisfy the alignment requirement ofT
, then the resulting pointer value is unspecified.Otherwise, if the original pointer value points to an object
a
, and there is an objectb
of typeT
(ignoring cv-qualification) that is pointer-interconvertible witha
, the result is a pointer tob
.Otherwise, the pointer value is unchanged by the conversion.
Assuming that buffer
is suitably aligned (you'd be in trouble well before this point if it's not), the first bullet is inapplicable. The second bullet is likewise inapplicable as there's no pointer-interconvertiblity here. It follows that we hit the third bullet - "the pointer value is unchanged by the conversion" and remains "pointer to the first element of buffer
".
Thus, b
does not point to the Foo
object x
; it points instead to the first char
element of buffer
, even though its type is Foo*
. It therefore cannot be used to access x
; attempting to do so yields undefined behavior (for the non-static data member case, by omission from [expr.ref]; for the non-static member function case, by [class.mfct.non-static]/2).
To recover a pointer to x
from b
, std::launder
can be used:
b = std::launder(b); // value of b is now "pointer to x"
// and can be used to access x