In C++, it is legal to reinterpret_cast
a pointer to a standard-layout type S
to a pointer to any of the members of S
, possibly using the offsetof
macro. If S
only has one member, then offsetof
is not necessary and this is legal:
struct S {
int x;
};
static_assert(std::is_standard_layout_v<S>);
void f(S s) {
// this is legal for standard-layout classes, offsetof the first member is zero
S *s0 = &s;
int *x0 = reinterpret_cast<int *>(s0);
}
But now suppose I have an array of S
:
S arr_s[10];
I can legally let the array decay to a pointer
S *s0 = arr_s;
and I can legally cast the pointer to an int *
:
int *x0 = reinterpret_cast<int *>(s0);
Does that mean that I can access the whole array via x0
? Is it legal to index x0
beyond 0? I am pretty sure the answer is no, because S
may have more than one member. But if we restrict ourselves to the case where S
has exactly one member, can I somehow legally treat an array of S
as an array of int
?
Yes.
No. You can form, dereference and access through
x0+0
. You can also formx0+1
, but it will be the one-past-the-object pointer and may not be dereferenced. You can't form pointers to any other index. Theint*
that you receive from the cast points to a singleint
object that is not part of an array ofint
. Such cases are treated as if they belonged to an array of size1
and pointer arithmetic is only defined inside that array.No, the layout of the class has no impact at all, except that for a non-standard layout class the
reinterpret_cast
itself will not work as intended and even accessing at index0
or any pointer arithmetic (including+0
) will be UB, because in that casereinterpret_cast
will not even result in a pointer to theint
object. You would then be trying to access or do pointer arithmetic on an object of a type that is not similar to the (pointer-removed) expression type.There is currently no way to achieve what you want.
This is technically not possible beyond the first element either at the moment. But that is a defect in the standard. It currently doesn't correctly specify that
reinterpret_cast<unsigned char*>
should yield a pointer to the object representation (which is the only way thatoffset
could be used). The details of this specification will matter in terms of whichoffsetof
constructs have UB and which have not. This is not straight-forward.All of this is in terms of standard guarantees (C++17 or later). Whether this will cause problems on compilers in practice is a different question. In practice (I think) you are probably fine on current compilers as long as you make sure that all alignment requirements are fulfilled and sizes match. Note that the latter is not guaranteed by only having one member in the class. There could still be padding at the end.