Can std::launder be used to convert an object pointer to its enclosing array pointer?

  • A+
Category:Languages

The current draft standard (and presumably C++17) say in [basic.compound/4]:

[ Note: An array object and its first element are not pointer-interconvertible, even though they have the same address. — end note ]

So a pointer to an object cannot be reinterpret_cast'd to get its enclosing array pointer.

Now, there is std::launder, [ptr.launder/1]:

template<class T> [[nodiscard]] constexpr T* launder(T* p) noexcept;

Requires: p represents the address A of a byte in memory. An object X that is within its lifetime and whose type is similar to T is located at the address A. All bytes of storage that would be reachable through the result are reachable through p (see below).

And the definion of reachable is in [ptr.launder/3]:

Remarks: An invocation of this function may be used in a core constant expression whenever the value of its argument may be used in a core constant expression. A byte of storage is reachable through a pointer value that points to an object Y if it is within the storage occupied by Y, an object that is pointer-interconvertible with Y, or the immediately-enclosing array object if Y is an array element. The program is ill-formed if T is a function type or cv void.

Now, at first sight, it seems that std::launder is can be used to do the aforementioned conversion, because of the part I've put emphasis.

But. If p points to an object of an array, the bytes of the array is reachable according to this definition (even though p is not pointer-interconvertible to array-pointer), just like the result of the launder. So, it seems that the definition doesn't say anything about this issue.

So, can std::launder be used to convert an object pointer to its enclosing array pointer?

 


This depends on whether the enclosing array object is a complete object, and if not, whether you can validly access more bytes through a pointer to that enclosing array object (e.g., because it's an array element itself, or pointer-interconvertible with a larger object, or pointer-interconvertible with an object that's an array element). The "reachable" requirement means that you cannot use launder to obtain a pointer that would allow you to access more bytes than the source pointer value allows, on pain of undefined behavior. This ensures that the possibility that some unknown code may call launder does not affect the compiler's escape analysis.

I suppose some examples could help. Each example below reinterpret_casts a int* pointing to the first element of an array of 10 ints into a int(*)[10]. Since they are not pointer-interconvertible, the reinterpret_cast does not change the pointer value, and you get a int(*)[10] with the value of "pointer to the first element of (whatever the array is)". Each example then attempts to obtain a pointer to the entire array by calling std::launder on the cast pointer.

int x[10]; auto p = std::launder(reinterpret_cast<int(*)[10]>(&x[0]));  

This is OK; you can access all elements of x through the source pointer, and the result of the launder doesn't allow you to access anything else.

int x2[2][10]; auto p2 = std::launder(reinterpret_cast<int(*)[10]>(&x2[0][0]));  

This is undefined. You can only access elements of x2[0] through the source pointer, but the result (which would be a pointer to x2[0]) would have allowed you to access x2[1], which you can't through the source.

struct X { int a[10]; } x3, x4[2]; // assume no padding auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x3.a[0])); // OK 

This is OK. Again, you can't access through a pointer to x3.a any byte you can't access already.

auto p4 = std::launder(reinterpret_cast<int(*)[10]>(&x4[0].a[0]));  

This is (intended to be) undefined. You would have been able to reach x4[1] from the result because x4[0].a is pointer-interconvertible with x4[0], so a pointer to the former can be reinterpret_cast to yield a pointer to the latter, which then can be used for pointer arithmetic. See https://wg21.link/LWG2859.

struct Y { int a[10]; double y; } x5; auto p3 = std::launder(reinterpret_cast<int(*)[10]>(&x5.a[0]));  

And this is again undefined, because you would have been able to reach x5.y from the resulting pointer (by reinterpret_cast to a Y*) but the source pointer can't be used to access it.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: