Search code examples
c++arrayscastingstandard-layout

Casting an array of standard-layout objects to array of elements


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?


Solution

  • and I can legally cast the pointer to an int *:

    Yes.

    Does that mean that I can access the whole array via x0? Is it legal to index x0 beyond 0?

    No. You can form, dereference and access through x0+0. You can also form x0+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. The int* that you receive from the cast points to a single int object that is not part of an array of int. Such cases are treated as if they belonged to an array of size 1 and pointer arithmetic is only defined inside that array.

    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?

    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 index 0 or any pointer arithmetic (including +0) will be UB, because in that case reinterpret_cast will not even result in a pointer to the int 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.

    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.

    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 that offset could be used). The details of this specification will matter in terms of which offsetof 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.