Let's start with the simplest case:
IA[3] = {A[2].i = 1}; I AA[3][3] = {AA[2][2].i = 1};
Both of them are UB, due to a violation of [basic.life]. You get access to the value of the object before it begins life. I does not have a trivial default constructor and therefore cannot be evacuated. Therefore, the lifetime of an object begins only after the completion of the constructor. The elements of array A have not yet been created when you access the elements of this array.
Therefore, you call UB, referring to an object not yet built.
Now two other cases are more complicated:
int a[3] = {a[2] = 1}; int aa[3][3] = {aa[2][2] = 1};
See, int allows "empty initialization" as defined by [basic.life] / 1. Storage was purchased for A and aa . Therefore, int a[3] is a valid array of int objects, although aggregate initialization has not yet begun. Thus, access to an object and even setting its state is not UB.
The order of operations here is fixed. Even pre-C ++ 17, the initialization of initializer list items is sequenced before aggregation initialization is called, as specified in [dcl.init.list] / 4. Elements in the aggregate that are not listed in the initialization list here will be populated as if in typename{} constructs. int{} means value-initialize int , resulting in 0.
Therefore, even if you set a[2] and aa[2][2] , they must be immediately overwritten by unit initialization.
Therefore, all these compilers are erroneous. The answer should be:
1 0 0 1 0 0 0 0 0 0 0 0
Now provided, it's all very stupid, and you shouldn't do that. But from a purely linguistic point of view, this is clearly defined behavior.