Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
97 views
in Technique[技术] by (71.8m points)

c++ - 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?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...