As I understand it, if we have an instance of X, we can take its address and cast it to char* and look at the content of X as if it was an array of bytes.
The standard doesn't actually allow this currently. Casting to char*
will not change the pointer value (per [expr.static.cast]/13) and as a result you will not be allowed to apply pointer arithmetic on it as it violates [expr.add]/4 and/or [expr.add]/6.
This is however often assumed to be allowed in practice and probably considered a defect in the standard. The paper P1839 by Timur Doumler and Krystian Stasiowski is trying to address that.
But even applying the proposed wording in this paper (revision P1839R5)
X x;
char* p = reinterpret_cast<char*>(&x.obj) + sizeof(Object);
*((int*)p) = 1234; // write an int into buf
will have undefined behavior, at least assuming I am interpreting its proposed wording and examples correctly. (I might not be though.)
First of all, there is no guarantee that buf
will be correctly aligned for an int
. If it isn't, then the cast (int*)p
will produce an unspecified pointer value. But also, there is no guarantee in general that there is no padding between obj
and buf
.
Even if you assume correct alignment and no padding, because e.g. you have guarantees from your ABI or compiler, there are still problems.
First, the proposal would only allow unsigned char*
, not char*
or std::byte*
, to access the object representation. See "Known issues" section.
Second, after fixing that, p
would be a pointer one-past the object representation of obj
, so it doesn't point to an object. As a consequence the cast (int*)p
cannot point to any int
object that might have been implicitly created in buf
when X x;
's lifetime started. Instead [expr.static.cast]/13 will apply and the value of the pointer remains unchanged.
Trying to dereference the int*
pointer pointing one-past-the-end of the object representation of obj
will then cause undefined behavior (as it is not pointing to an object).
You also can't save this using std::launder
on the pointer, because a pointer to an int
nested inside buf
would give you access to bytes which are not reachable through a pointer to the object representation of buf
, violating std::launder
's precondition, see [ptr.launder]/4.
In a broader picture, if you look at how e.g. std::launder
is specified, it seems to me that the intention is definitively not to allow this. The way it is specified, it is impossible to use a pointer (in)to a member of a class (except the first if standard layout) to access memory of other (non-overlapping) members. This specifically seems to be intended to allow a compiler to do optimization by pointer analysis based on assuming that these other members are unreachable. (I don't know whether there is any compiler actually doing this though.)