Your second case is undefined behavior, you are no longer using aggregate initialization, it is still list initialization but in this case you have a user defined constructor which is being called. In order to pass the second argument to your constructor it needs to evaluate foo.i
but it is not initialized yet since you have not yet entered the constructor and therefore you are producing an indeterminate value and producing an indeterminate value is undefined behavior.
We also have section 12.7
Construction and destruction [class.cdtor] which says:
For an object with a non-trivial constructor, referring to any non-static member or base class of the object
before the constructor begins execution results in undefined behavior [...]
So I don't see a way of getting your second example to work like your first example, assuming the first example is indeed valid.
Your first case seems like it should be well defined but I can not find a reference in the draft standard that seems to make that explicit. Perhaps it is defect but otherwise it would be undefined behavior since the standard does not define the behavior. What the standard does tell us is that the initializers are evaluated in order and the side effects are sequenced, from section 8.5.4
[dcl.init.list]:
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack
expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and
side effect associated with a given initializer-clause is sequenced before every value computation and side
effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list. [...]
but we don't have an explicit text saying the members are initialized after each element is evaluated.
MSalters argues that section 1.9
which says:
Accessing an object designated by a volatile glvalue (3.10), modifying an object, calling a library I/O
function, or calling a function that does any of those operations are all side effects, which are changes in the
state of the execution environment. [...]
combined with:
[...]very value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it [...]
Is sufficient to guarantee each member of the aggregate is initialized as the elements of the initializer list are evaluated. Although this would be not apply prior to C++11 since the order of evaluation of the initializer list was unspecified.
For reference if the standard does not impose a requirement the behavior is undefined from section 1.3.24
which defines undefined behavior:
behavior for which this International Standard imposes no requirements
[ Note: Undefined behavior may be expected when this International Standard omits any explicit definition of
behavior or [...]
Update
Johannes Schaub points out defect report 1343: Sequencing of non-class initialization and std-discussion threads Is aggregate member copy-initialization associated with the corresponding initializer-clause? and Is copy-initialization of an aggregate member associated with the corresponding initializer-clause? which are all relevant.
They basically point out that the first case is currently unspecified, I will quote Richard Smith:
So the only question is, is the side-effect of initializing s.i
"associated with" the evaluation of the full-expression "5"? I think
the only reasonable assumption is that it is: if 5 were initializing a
member of class type, the constructor call would obviously be part of
the full-expression by the definition in [intro.execution]p10, so it
is natural to assume that the same is true for scalar types.
However, I don't think the standard actually explicitly says this
anywhere.
So although as indicated in several places it looks like current implementations do what we expect, it seems unwise to rely on it until this is officially clarified or the implementations provide a guarantee.
C++20 Update
With the Designated Initialization proposal: P0329 the answer to this question changes for the first case. It contains the following section:
Add a new paragraph to 11.6.1 [dcl.init.aggr]:
The initializations of the elements of the aggregate are evaluated in the element order. That is,
all value computations and side effects associated with a given element are sequenced before
We can see this is reflected in the latest draft standard
mystruct x {++i, ++i};
is well-defined thoughmystruct y(++i, ++i);
is not. Nothing more than that. Since your code is not convered by the specification, thus I think it invokes UB. – Aerodrome