Placement new + reinterpret_cast in C++14: well-formed?

228 Views Asked by At

Consider the following example in C++14:

alignas(T) unsigned char data[sizeof(T)];
new (data) T();
T* p = reinterpret_cast<T*>(data);
p->something();  // UB?

Is this code legal, or are the Strict Aliasing rules being violated, since unsigned char* may not be aliased by T*? If it's legal, what parts of the Standard explicitly say so?

cppreference has a similar example which claims std::launder must be used in C++17. What does this mean for C++14, where we don't have std::launder?

    // Access an object in aligned storage
    const T& operator[](std::size_t pos) const
    {
        // Note: std::launder is needed after the change of object model in P0137R1
        return *std::launder(reinterpret_cast<const T*>(&data[pos]));
    }
3

There are 3 best solutions below

0
user1011113 On BEST ANSWER

Thanks everyone for your replies! I will try to answer the question combining the knowledge I've gotten from the responses.

There is no Strict Aliasing violation

As per basic.life#2:

The lifetime of an array object starts as soon as storage with proper size and alignment is obtained, and its lifetime ends when the storage which the array occupies is reused or released

Therefore, after the placement new call, the char array no longer contains objects of type char. It contains a newly created object of type T.

Later, we access the object of type T via a T*, which is a valid alias. Therefore, Strict Aliasing rules are not violated here.

Lifetime

The real problem is lifetime. When we reinterpret_cast<T*>(data), we are using a pointer (data) that points to expired data, since it has been replaced by the new object. There is no guarantee that the data pointer is "updated" to point to the newly-created object.

  • In C++14, the only way to do this legally is by accessing the object T via the pointer returned by placement new.
  • In C++17, additionally, we can access the object via the old pointer, data, as long as we launder it via std::launder. This allows not having to store the pointer returned by placement new.
5
G. Sliepen On

The other answers and comments have already addressed the well-formedness issue. However, you can avoid this issue entirely; you can make use of the fact that the placement-new operator returns a valid pointer to a T. So instead of writing:

new (data) T();
T* p = reinterpret_cast<T*>(data);

You could write:

T* p = new (data) T();

So std::launder() is not necessary at all in this case, and this is valid code in C++14 and earlier.

5
MSalters On

Yup. As you figured out, this is a case where you need C++17 std::launder. What it means for C++14 is that you need to store the return value of new, even if that brings some overhead.

This isn't exactly a surprise. One reason why std::launder was introduced, and all the associated changes to C++17 are justified, is because of the overhead you see in C++14.

Note that this overhead didn't really materialize a decade ago, because it's mostly missed-optimizations overhead. It only started to count when optimizers started to get smarter. The flip side is that illegal code like in this question did not tend to break in old compilers, even though it was never legal.