29

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};
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
Jonathan Mee
  • 37,899
  • 23
  • 129
  • 288
  • @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. – Jonathan Mee Nov 20 '15 at 14:22
  • 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. – NathanOliver Nov 20 '15 at 14:34
  • 1
    @NathanOliver 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. – Jonathan Mee Nov 20 '15 at 15:35
  • 1
    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 aggregate – M.M Nov 23 '15 at 02:40
  • @M.M 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. – Shafik Yaghmour Nov 23 '15 at 17:58
  • 3
    This is a braced init list rather than an `initializer_list`, isn't it? – Baum mit Augen Jul 10 '18 at 13:01
  • 1
    @BaummitAugen [Yes it is](https://stackoverflow.com/questions/37682392/what-is-a-curly-brace-enclosed-list-if-not-an-intializer-list) – NathanOliver Jul 10 '18 at 13:02
  • 1
    @FrançoisAndrieux I have flagged to see if we can gets these merged together. – NathanOliver Jul 10 '18 at 13:09
  • 3
    @NathanOliver Wow... I am dumb. Thanks for the link. – Jonathan Mee Jul 10 '18 at 13:14
  • @FrançoisAndrieux The author agrees and will update the answer later. – Rakete1111 Jul 10 '18 at 14:39
  • @Rakete1111 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. – Jonathan Mee Jul 10 '18 at 14:41
  • Possible duplicate of [Is it defined behavior to reference an early member from a later member expression during aggregate initialization?](https://stackoverflow.com/questions/32940847/is-it-defined-behavior-to-reference-an-early-member-from-a-later-member-expressi) – xskxzr Jul 10 '18 at 19:08
  • 1
    @xskxzr While I agree those are similar, that's definitely not a duplicate as it has nothing to do with arrays. – Jonathan Mee Jul 10 '18 at 20:22
  • 1
    @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. – Shafik Yaghmour Jul 15 '18 at 20:05
  • @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. – Shafik Yaghmour Jul 16 '18 at 16:58

1 Answers1

23

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.

Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • What about http://eel.is/c++draft/dcl.init.aggr#6? It says it rather explicitly that it's allowed. – Rakete1111 Jul 10 '18 at 13:08
  • @Rakete1111 This was very recently added IIRC, will update when I have a chance to look it up again. – Shafik Yaghmour Jul 10 '18 at 13:11
  • Oh you're right, it was added with designated initializer lists :) Thanks. – Rakete1111 Jul 10 '18 at 13:15
  • @Rakete1111 I recently did [a twitter poll on a similar one](https://twitter.com/shafikyaghmour/status/1011665443679924224) and this question is [actually similar to this one](https://stackoverflow.com/q/32940847/1708801) – Shafik Yaghmour Jul 10 '18 at 17:09
  • `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? – xskxzr Jul 16 '18 at 14:26
  • @xskxzr I believe you are correct, I misread the proposal in light of the analysis [here](https://groups.google.com/a/isocpp.org/d/msg/std-discussion/lH86B0MhbAc/Kedqd0qwEMgJ) but re-reading `, including the constituent expressions of the initializer` the comma is not a separate item. – Shafik Yaghmour Jul 16 '18 at 16:42
  • [dcl.init.list]p4 still applies. – T.C. Jul 20 '18 at 05:45
  • @T.C. in [this discussion](https://groups.google.com/a/isocpp.org/d/msg/std-discussion/lH86B0MhbAc/Kedqd0qwEMgJ) 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. – Shafik Yaghmour Jul 20 '18 at 13:16
  • 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). – T.C. Jul 20 '18 at 15:38
  • @T.C. 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. – Shafik Yaghmour Jul 20 '18 at 17:09
  • 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_." – T.C. Jul 20 '18 at 17:13
  • 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. – T.C. Jul 20 '18 at 17:16
  • @T.C. I see what your saying, `P0507` did not really resolve the [question discussed here](https://groups.google.com/a/isocpp.org/d/msg/std-discussion/lH86B0MhbAc/Kedqd0qwEMgJ) ... 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"? ` – Shafik Yaghmour Jul 20 '18 at 17:25