Reputation: 3432
In the following program:
#include <iostream>
struct I {
int i;
I(){i=2;}
I(int _i){i=_i;}
};
int a[3] = {a[2] = 1};
int aa[3][3] = {aa[2][2] = 1};
I A[3] = {A[2].i = 1};
I AA[3][3] = {AA[2][2].i = 1};
int main(int argc, char **argv) {
for (int b : a) std::cout << b << ' ';
std::cout << '\n';
for (auto &bb : aa) for (auto &b : bb) std::cout << b << ' ';
std::cout << '\n';
for (auto &B : A) std::cout << B.i << ' ';
std::cout << '\n';
for (auto &BB : AA) for (auto &B : BB) std::cout << B.i << ' ';
std::cout << '\n';
return 0;
}
The output is
1 0 0
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2
from http://ideone.com/1ueWdK with clang3.7
but the result is :
0 0 1
1 0 0 0 0 0 0 0 1
1 2 2
1 2 2 2 2 2 2 2 2
on http://rextester.com/l/cpp_online_compiler_clang also with clang 3.7.
On my own ubuntu, gcc 6.2 givs an internal compiler error on the construct int aa[3][3] = {aa[2][2] = 1}
.
I'm assuming this is undefined behavior, but cannot find a definitive statement in the standard.
The question is:
Whether the evaluation order of the side effects on the assignment in the initializer list (e.g. a[2] = 1
) and initialization of the actual element of the array (e.g. a[2]
) defined in the standard?
It is explicitly stated as defined or undefined? Or does it become undefined just because it is not explicitly defined?
Or does the construct has defined or undefined behavior due to other reason aside from the evaluation order?
Upvotes: 5
Views: 249
Reputation: 473272
Let's start with the simplest case:
I A[3] = {A[2].i = 1}; I AA[3][3] = {AA[2][2].i = 1};
Both of these are UB, due to a violation of [basic.life]. You are accessing the value of an object before its lifetime has begun. I
does not have a trivial default constructor, and therefore cannot be vacuously initialized. Therefore, the object's lifetime only begins once a constructor has completed. The elements of the A
array have not yet been constructed when you are accessing elements of that array.
Therefore, you are invoking UB by accessing a not-yet-constructed object.
Now, the other two cases are more complex:
int a[3] = {a[2] = 1}; int aa[3][3] = {aa[2][2] = 1};
See, int
permits "vacuous initialization", as defined by [basic.life]/1. Storage for a
and aa
has been acquired. Therefore, int a[3]
is a valid array of int
objects, even though aggregate initialization has not yet begun. So accessing the object and even setting its state is not UB.
The order of operations here is fixed. Even pre-C++17, the initialization of the elements of the initializer list is sequenced before the aggregate initialization is invoked, as stated in [dcl.init.list]/4. Elements in the aggregate which are not listed in the initialization list here will be filled in as if by typename{}
constructs. int{}
means to value-initialize an int
, which results in 0.
So even though you set a[2]
and aa[2][2]
, they should immediately be overwritten via aggregate initialization.
Therefore, all of these compilers are wrong. The answer should be:
1 0 0 1 0 0 0 0 0 0 0 0
Now granted, this is all very stupid and you shouldn't do it. But from a pure language perspective, this is well-defined behavior.
Upvotes: 5