Search code examples
c++pointerslanguage-lawyerc++20c++23

Given two objects of different types and their relative location in memory, can I derive a pointer to one object from a pointer to the other?


In the following example, we have two objects that are of different types but happen to be located right next to each other in memory.

struct X {
    alignas(long) int val = 1;
};

struct Y {
    long val = 2;
};

[...]

std::byte* buf = new std::byte[sizeof(X) + sizeof(Y)];
X* x = new (buf) X();
new (buf + sizeof(X)) Y();

Given x, and our knowledge of where the instance of Y is actually located in memory relative to x, is there any way to derive a pointer to the instance of Y from x that complies with C++20 or C++23?

In other words, is the following code standards compliant, or is there any way to make it standards compliant:

Y* y = std::launder(reinterpret_cast<Y*>(reinterpret_cast<std::byte*>(x) + sizeof(X)));
y->val = 3;

I suspect the code is not standards compliant in either C++20 or C++23 for a couple of reasons:

(1) P1839R5 indicates that it isn't actually possible to obtain a usable pointer to an object representation in the first place.

(2) Even given the revised pointer arithmetic rules from P1839R5, it still seems like you wouldn't be able to go from a pointer to one object representation to a different object representation.


Solution

  • No, there is no way to do that. (Except maybe with the help of implicit object creation of a suitable aggregate type, see my question here.)

    std::launder specifically has a precondition that it won't make any bytes reachable that wouldn't be reachable from the original pointer via reinterpret_cast and pointer arithmetic inside arrays.

    With reinterpret_cast only objects which are pointer-interconvertible from one another can be reached without std::launder, but in particular neither the std::byte object, nor the std::byte array object at the same address as the X object are pointer-interconvertible with the X object. (See [basic.compound]/4.) Therefore a pointer to the underlying array (elements) cannot be obtained without std::launder and using std::launder is impossible due to its precondition.

    Specifically, the addition in reinterpret_cast<std::byte*>(x) + sizeof(X) has undefined behavior because reinterpret_cast<std::byte*>(x) points to the X object, not a std::byte object, and so [expr.add]/6 is violated. Adding std::launder is impossible due to its precondition.

    The linked paper attempts to properly specify access to object representations, which hasn't actually been specified in the way it is used in practice (e.g. by casting to unsigned char*), but only via memcpy/std::bit_cast to a suitable buffer. If it is specified, I would expect it to be in a way that shouldn't be able to work around the reachability precondition placed on std::launder, which seems to make it impossible to reach the Y object from the X object in your example intentionally. It should be possible to access only the object representation of the X object itself from a pointer to it, not the object representations of the underlying array or the Y object.