Order of Evaluation of Elements in List-Initialization

Evaluation order of elements in an initializer list

It seems to me that the quote is relevant (the compiler sees an initializer list):

8.5/14,16:

The initialization that occurs in the form

T x = a;

as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.

.

.

The semantics of initializers are as follows[...]: If the initializer is a braced-init-list, the object is list-initialized (8.5.4).

(more details in std::initializer_list as function argument and Folds (ish) In C++11)

Moreover any {}-list should be sequenced (the standard uses a very strong wording about this fact. See also http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1030).

So it's probably a GCC bug (fixed after gcc v4.9.0).

Indeed, trying various GCC version, I get:

GCC      with --std=c++11   without (--std=c++98)
4.7.3 fg gf <-
4.8.1 fg gf <-
4.8.2 fg gf <-
4.9.0 fg gf <-
4.9.2 fg fg
5.1.0 fg fg
5.2.0 fg fg
6.1.0 fg fg

Extended initializer lists are only available with C++11 but GCC compiles the code anyway (with a warning, e.g. see gcc -Wall -Wextra vs gcc -Wall -Wextra -std=c++11).

Order of evaluation of elements in list-initialization

Answering my own question. Deleting the question would not be a good idea, as someone might have the same question in the future.

Yes. It is a bug in the GCC compiler.

  • Bug 51253 - [C++11][DR 1030] Evaluation order (sequenced-before relation) among initializer-clauses in braced-init-list

taken from @Johannes Schaub's comment to the question.

Member initializer list. Order of arguments evaluation

No, this evaluation order that would potentially cause you to leak is guaranteed not to happen. The expression within each mem-initializer is a full-expression ([intro.execution] §5.3). And [intro.execution] §9 tells us

Every value computation and side effect associated with a full-expression is sequenced before every value computation and side effect associated with the next full-expression to be evaluated.

So in other words, the evaluation of each expression used to initialize a member is completed before evaluation of the expression for the next member starts.

std::initializer_list and order of evaluation of the elements

According to C++11 § 8.5.4 [dcl.init.list] paragraph 4:

4 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.

As far as I know GCC 4.8.1 has a bug relative to evaluation of initializers. I described it here

http://cpp.forum24.ru/?1-3-0-00000063-000-0-0-1378892425

Though the text is written in Russion but it can be simply translated in English by using for example google translate.

Evaluation order in initialization

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.

Ordering in an initialization in C, C++

In all these two cases

goo b = { f(), g() };
goo c { f(), g() }; /* C++11 */

the order of evaluation is determined from left to right and all side effects shall be applied before the next initializer.

From the C++ STandard

4 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.

However in C there is other rule

The evaluations of the initialization list expressions are
indeterminately sequenced with respect to one another and thus the
order in which any side effects occur is unspecified.

evaluation order initialization array in c++

They are evaluated sequentially. C++11 § 8.5.4 [dcl.init.list] paragraph 4:

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.

Given that vector has an initializer_list constructor, you could simplify your function to:

template <typename ... T>
auto make_vector(T ... t) ->
std::vector< typename std::common_type<T...>::type >
{
return { static_cast<typename std::common_type<T...>::type>(t)... };
}

and not have to worry about arcane initialization semantics ;)

order of evaluation of arguments to a constructor

This is a bug in GCC (thanks to Casey for the link). The paragraph you quoted applies in general to list initialization, where the terms are defined pretty clearly in paragraph 8.5.4/1:

List-initialization is initialization of an object or reference from a braced-init-list. Such an initializer is
called an initializer list, and the comma-separated initializer-clauses of the list are called the elements of the
initializer list.

There is no reason to believe this should apply only to the invocation of an initializer list constructor. Also, the note in the paragraph you quoted clarifies that:

This evaluation ordering holds regardless of the semantics of the initialization; for example, it applies when the elements of the initializer-list are interpreted as arguments of a constructor call, even though ordinarily there are no sequencing constraints on the arguments of a call.

Constructor initialization-list evaluation order

It depends on the order of member variable declaration in the class. So a_ will be the first one, then b_ will be the second one in your example.



Related Topics



Leave a reply



Submit