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 to
b
.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