Can I Reference Previous Members of an Initializer List?
Asked Answered
O

1

31

Say I want to refer to a member of an initializer_list that I already defined. Can I do it?

This code compiles and gives the expected: "13 55 " in both Visual Studio and gcc, I'd just like to know that it's legal:

const int foo[2] = {13, foo[0] + 42};
Outdoor answered 20/11, 2015 at 13:49 Comment(15)
@ShafikYaghmour While this question also references an initializer_list it does so in a struct format rather than an array format. There are brief mentions of arrays in the answers but all of them are directed at struct related answers, which (potentially) initialize in a completely different method. In my mind this is not a duplicate and I'd certainly like to see some analysis of whether it is legal in an array context.Outdoor
I reopened as it could be argued that the answer for an array might not be exactly what was in the other question. I have a felling any answer you get is going to look very similar though.Secundines
@Secundines Thanks, I do agree. But it's a completely separate question. To read through pages of stuff about structs to find an answer on arrays is not constructive.Outdoor
DR1343 looks like it doesn't quite go far enough; what's needed is an absolute statement that for aggregate initialization, an initializer must not be evaluated before initialization of the previous element is complete. As Shafik says, at the moment there does not seem to be any wording to prevent all elements of the list being evaluated, and then the results applied to the aggregateClomp
@Clomp well it is hard to know how far it will go until it is resolved. Currently, this one does not even have a proposed wording.Bobette
This is a braced init list rather than an initializer_list, isn't it?Malignity
@BaummitAugen Yes it isSecundines
@FrançoisAndrieux I have flagged to see if we can gets these merged together.Secundines
@Secundines Wow... I am dumb. Thanks for the link.Outdoor
@FrançoisAndrieux The author agrees and will update the answer later.Woolfell
@Woolfell I agree, and I've gone ahead and accepted... but it might make more sense to add this to my original question :/ I'm not totally sure.Outdoor
Possible duplicate of Is it defined behavior to reference an early member from a later member expression during aggregate initialization?Scharaga
@Scharaga While I agree those are similar, that's definitely not a duplicate as it has nothing to do with arrays.Outdoor
@cpplearner interesting, I updated my answer. I missed that proposal, although the C++20 changes I added to my answer also make this legal as well. It feels like they are redundant now though.Bobette
@cpplearner I mis-read the proposal a comma threw me off. The original analysis also made sense to me so my initial reading was biased by that.Bobette
B
24

So what we have here is aggregate initialization covered in section 8.5.1 of the draft C++ standard and it says:

An aggregate is an array or a class [...]

and:

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 [...]

Although it seems reasonable that side effects from initializing each member of the aggregate should be sequenced before the next, since each element in the initializer list is a full expression. The standard does not actually guarantee this we can see this from defect report 1343 which says:

The current wording does not indicate that initialization of a non-class object is a full-expression, but presumably should do so.

and also notes:

Aggregate initialization could also involve more than one full-expression, so the limitation above to “initialization of a non-class object” is not correct.

and we can see from a related std-discussion topic Richard Smith says:

[intro.execution]p10: "A full-expression is an expression that is not a subexpression of another expression. [...] If a language construct is defined to produce an implicit call of a function, a use of the language construct is considered to be an expression for the purposes of this definition."

Since a braced-init-list is not an expression, and in this case it does not result in a function call, 5 and s.i are separate full-expressions. Then:

[intro.execution]p14: "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 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 this is currently not specified by the standard and can not be relied upon, although I would be surprised if an implementation did not treat it the way you expect.

For a simple case like this something similar to this seems a better alternative:

constexpr int value = 13 ;
const int foo[2] = {value, value+42};

Changes In C++17

The proposal P0507R0: Core Issue 1343: Sequencing of non-class initialization clarifies the full-expression point brought up here but does not answer the question about whether the side-effect of initialization is included in the evaluation of the full-expression. So it does not change that this is unspecified.

The relevant changes for this question are in [intro.execution]:

A constituent expression is defined as follows:

(9.1) — The constituent expression of an expression is that expression.

(9.2) — The constituent expressions of a braced-init-list or of a (possibly parenthesized) expression-list are the constituent expressions of the elements of the respective list.

(9.3) — The constituent expressions of a brace-or-equal-initializer of the form = initializer-clause are the constituent expressions of the initializer-clause. [ Example:

struct A { int x; };
struct B { int y; struct A a; };
B b = { 5, { 1+1 } };

The constituent expressions of the initializer used for the initialization of b are 5 and 1+1. —end example ]

and [intro.execution]p12:

A full-expression is

(12.1) — an unevaluated operand (Clause 8),

(12.2) — a constant-expression (8.20),

(12.3) — an init-declarator (Clause 11) or a mem-initializer (15.6.2), including the constituent expressions of the initializer,

(12.4) — an invocation of a destructor generated at the end of the lifetime of an object other than a temporary object (15.2), or

(12.5) — an expression that is not a subexpression of another expression and that is not otherwise part of a full-expression.

So in this case both 13 and foo[0] + 42 are constituent expression which are part of a full-expression. This is a break from the analysis here which posited that they would each be their own full-expressions.

Changes In C++20

The Designated Initialization proposal: P0329 contains the following addition which seems to make this well defined:

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 those of any element that follows it in order.

We can see this is reflected in the latest draft standard.

Bobette answered 20/11, 2015 at 14:46 Comment(13)
What about eel.is/c++draft/dcl.init.aggr#6? It says it rather explicitly that it's allowed.Woolfell
@Woolfell This was very recently added IIRC, will update when I have a chance to look it up again.Bobette
Oh you're right, it was added with designated initializer lists :) Thanks.Woolfell
@Woolfell I recently did a twitter poll on a similar one and this question is actually similar to this oneBobette
13 and foo[0] + 42 belong to one full-expression. So why is the side effect of one initializer sequenced before that of the other?Scharaga
@Scharaga I believe you are correct, I misread the proposal in light of the analysis here but re-reading , including the constituent expressions of the initializer the comma is not a separate item.Bobette
[dcl.init.list]p4 still applies.Barnhill
@Barnhill in this discussion Richard dd not seem to believe that was sufficient to show that the initialization of the members was explicitly covered. Also why would the designated initializer proposal add that clause if it was.Bobette
It's literally the same words, just substituting "initializer-clause" for "full-expression". Either they both cover this case or both don't, but regardless P0507 has zero effect on sequencing. P0329 explains right after the paragraph why it did that (to cover elements not explicitly initialized, which is not the case here).Barnhill
@Barnhill The P0329 change says The initializations of the elements and then says value computations and side effects associated with a given element of the aggregate which is not the same as [dcl.init.list]p4 which talks about initializer-clause and effects wrt to those.Bobette
That's not the point. Before P0507, those are arguably full-expressions, and "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." After P0507, they aren't full-expressions, but still "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."Barnhill
P0507 changed no behavior in this case. The initialization either is or isn't "associated with" the initializer-clause; whether the initializer-clause is a full-expression or not has no effect on sequencing.Barnhill
@Barnhill I see what your saying, P0507 did not really resolve the question discussed here ... because even if they are full-expressions it does not address So the only question is, is the side-effect of initializing s.i "associated with" the evaluation of the full-expression "5"? Bobette

© 2022 - 2024 — McMap. All rights reserved.