c++language-lawyerundefined-behaviorreinterpret-caststrict-aliasing

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


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]));
    }

Solution

  • 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.