tl;dr: the last two statements in your code above will always invoke undefined behavior, simply casting a pointer to a union to a pointer to one of its member types is generally fine because it doesn't really do anything (it's unspecified at worst, but never undefined behavior; note: we're talking about just the cast itself, using the result of the cast to access an object is a whole different story).
Depending on what T
ends up being, Struct<T>
may potentially be a standard-layout struct [class.prop]/3 in which case
T *aSP = reinterpret_cast<T *>(aS);
would be well-defined because a Struct<T>
would be pointer-interconvertible with its first member (which is of type T
) [basic.compound]/4.3. Above reinterpret_cast
is equivalent to [expr.reinterpret.cast]/7
T *aSP = static_cast<T *>(static_cast<void *>(aS));
which will invoke the array-to-pointer conversion [conv.array], resulting in a Struct<T>*
pointing to the first element of aS
. This pointer is then converted to void*
(via [expr.static.cast]/4 and [conv.ptr]/2), which is then converted to T*
, which would be legal via [expr.static.cast]/13:
A prvalue of type “pointer to cv1 void
” can be converted to a prvalue of type “pointer to cv2 T
”, where T
is an object type and cv2 is the same cv-qualification as, or greater cv-qualification than, cv1. If the original pointer value represents the address A
of a byte in memory and A
does not satisfy the alignment requirement of T
, then the resulting pointer value is unspecified. Otherwise, if the original pointer value points to an object a
, and there is an object b
of type T
(ignoring cv-qualification) that is pointer-interconvertible with a
, the result is a pointer to b
. Otherwise, the pointer value is unchanged by the conversion.
Similarly,
T *aUP = reinterpret_cast<T *>(aU);
would be well-defined in C++17 if Union<T>
is a standard-layout union and looks to be well-defined in general with the coming version of C++ based on the current standard draft, where a union and one of its members are always pointer-interconvertible [basic.compound]/4.2
All of the above is irrelevant, however, because
T valueS = aSP[9];
and
T valueU = aUP[9];
will invoke undefined behavior no matter what. aSP[9]
and aUP[9]
are (by definition) the same as *(aSP + 9)
and *(aUP + 9)
respectively [expr.sub]/1. The pointer arithmetic in these expressions is subject to [expr.add]/4
When an expression J
that has integral type is added to or subtracted from an expression P
of pointer type, the result has the type of P
.
- If
P
evaluates to a null pointer value and J
evaluates to 0, the result is a null pointer value.
- Otherwise, if
P
points to element x[i]
of an array object x
with n elements, the expressions P + J
and J + P
(where J
has the value j) point to the (possibly-hypothetical) element x[i+j]
if 0≤i+j≤n and the expression P - J
points to the (possibly-hypothetical) element x[i−j]
if 0≤i−j≤n.
- Otherwise, the behavior is undefined.
aSP
and aUP
do not point to an element of an array. Even if aSP
and aUP
would be pointer-interconvertible with T
, you'd only ever be allowed to access element 0 and compute the address of (but not access) element 1 of the hypothetical single-element array…