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…