Is there a (semantic) difference between the return value of placement new and the casted value of its operand?

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 cv2 T”, where T 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 and A does not satisfy the alignment requirement of T,
    then the resulting pointer value is unspecified.

  • Otherwise, if the original pointer value points to an object a, and there is an object b of type T (ignoring cv-qualification)
    that is pointer-interconvertible with a, 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

Leave a Comment