It is illegal 1. That’s an Undefined behavior in C++.
You are taking the members in an array fashion, but here is what the C++ standard says (emphasis mine):
[dcl.array/1]: …An object of array type contains a contiguously allocated non-empty set of N
subobjects of type T…
But, for members, there’s no such contiguous requirement:
[class.mem/17]: …;Implementation alignment requirements might cause two adjacent
members not to be allocated immediately after each other…
While the above two quotes should be enough to hint why indexing into a struct
as you did isn’t a defined behavior by the C++ standard, let’s pick one example: look at the expression (&thing.a)[2]
– Regarding the subscript operator:
[expr.post//expr.sub/1]:
A postfix expression followed by an expression in square brackets is a
postfix expression. One of the expressions shall be a glvalue of type
“array of T” or a prvalue of type “pointer to T” and the other shall
be a prvalue of unscoped enumeration or integral type. The result is
of type “T”. The type “T” shall be a completely-defined object type.66
The expressionE1[E2]
is identical (by definition) to((E1)+(E2))
Digging into the bold text of the above quote: regarding adding an integral type to a pointer type (note the emphasis here)..
[expr.add/4]: When an expression that has integral type is added to or subtracted from a
pointer, the result has the type of the pointer operand. If the
expressionP
points to elementx[i]
of an array objectx
with n elements, the expressionsP + J
andJ + P
(whereJ
has
the valuej
) point to the (possibly-hypothetical) elementx[i + j]
if0 ≤ i + j ≤ n
; otherwise, the behavior is undefined. …
Note the array requirement for the if clause; else the otherwise in the above quote. The expression (&thing.a)[2]
obviously doesn’t qualify for the if clause; Hence, Undefined Behavior.
On a side note: Though I have extensively experimented the code and its variations on various compilers and they don’t introduce any padding here, (it works); from a maintenance view, the code is extremely fragile. you should still assert that the implementation allocated the members contiguously before doing this. And stay in-bounds :-). But its still Undefined behavior….
Some viable workarounds (with defined behavior) have been provided by other answers.
As rightly pointed out in the comments, [basic.lval/8], which was in my previous edit doesn’t apply. Thanks @2501 and @M.M.
1: See @Barry’s answer to this question for the only one legal case where you can access thing.a
member of the struct via this parttern.