Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it defined behavior to reference an early member from a later member expression during aggregate initialization?

Consider the following:

struct mystruct {     int i;     int j; };  int main(int argc, char* argv[]) {     mystruct foo{45, foo.i};         std::cout << foo.i << ", " << foo.j << std::endl;      return 0; } 

Note the use of foo.i in the aggregate-initializer list.

g++ 5.2.0 outputs

45, 45

Is this well-defined behavior? Is foo.i in this aggregate-initializer always guaranteed to refer to the being-created structure's i element (and &foo.i would refer to that memory address, for example)?

If I add an explicit constructor to mystruct:

mystruct(int i, int j) : i(i), j(j) { } 

Then I get the following warnings:

main.cpp:15:20: warning: 'foo.a::i' is used uninitialized in this function [-Wuninitialized]      a foo{45, foo.i};                 ^ main.cpp:19:34: warning: 'foo.a::i' is used uninitialized in this function [-Wuninitialized]      cout << foo.i << ", " << foo.j << endl; 

The code compiles and the output is:

45, 0

Clearly this does something different, and I'm assuming this is undefined behavior. Is it? If so, why the difference between this and when there was no constructor? And, how can I get the initial behavior (if it was well-defined behavior) with a user-defined constructor?

like image 344
Claudiu Avatar asked Oct 05 '15 03:10

Claudiu


People also ask

What is designated initializer in C++?

Designated initialization is an extension of aggregate initialization and empowers you to directly initialize the members of a class type using their names. Designated initialization is a special case of aggregate initialization.

What is brace initialization?

If a class has non-default constructors, the order in which class members appear in the brace initializer is the order in which the corresponding parameters appear in the constructor, not the order in which the members are declared (as with class_a in the previous example).

What is aggregate type C++?

Formal definition from the C++ standard (C++03 8.5. 1 §1): An aggregate is an array or a class (clause 9) with no user-declared constructors (12.1), no private or protected non-static data members (clause 11), no base classes (clause 10), and no virtual functions (10.3).


2 Answers

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

like image 83
Shafik Yaghmour Avatar answered Oct 02 '22 15:10

Shafik Yaghmour


From [dcl.init.aggr] 8.5.1(2)

When an aggregate is initialized by an initializer list, as specified in 8.5.4, the elements of the initializer list are taken as initializers for the members of the aggregate, in increasing subscript or member order. Each member is copy-initialized from the corresponding initializer-clause.

emphasis mine

And

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.

Leads me to believe that each member of the class will be initialized in the order they are declared in the initializer-list and since foo.i is initialized before we evaluate it to initialize j this should be defined behavior.

This is also backed up with [intro.execution] 1.9(12)

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.

emphasis mine

In your second example we are not using aggregate initialization but list initialization. [dcl.init.list] 8.5.4(3) has

List-initialization of an object or reference of type T is defined as follows:
[...]
- Otherwise, if T is a class type, constructors are considered. The applicable constructors are enumerated and the best one is chosen through overload resolution (13.3, 13.3.1.7).

So now we would call your constructor. When calling the constructor foo.i has not been initialized so we are copying an uninitialized variable which is undefined behavior.

like image 37
NathanOliver Avatar answered Oct 02 '22 15:10

NathanOliver