24

Is this causing undefined behaviour? Specifically, the incrementing in the initializer list and how will that be evaluated.

class Wrinkle {
public:
    Wrinkle(int i) : a(++i), b(++i), x(++i) {}
private:
    int a;
    int x;
    int b;
};

The difference in order between the declaration of members and initializer list is intended since this is an example that would showcase exactly that difference, so please ignore it for now.

compor
  • 2,239
  • 1
  • 19
  • 29
Keerpich
  • 343
  • 1
  • 10
  • What are your expected values for `a`, `b` and `x` if you are doing `Wrinkle tmp{1}`? – mch Jan 19 '18 at 12:47
  • `Wrinkle(int i) : a(i+1), b(i+2), x(i+3) {}` – Bo Persson Jan 19 '18 at 12:48
  • 7
    @BoPersson That looks like the wrong order. I'd expect `a, x, b`. – melpomene Jan 19 '18 at 12:50
  • 11
    note that the members are initialized in the order they appear in the declaration not in the order they appear in the initializer list. Your compiler should show you a warning for this code. And imho thats a complete showstopper for what you want to do there: The initial values will change (unexpectetly) if you rearrange the declaration of the members. Thats a good way to confuse anybody refactoring your code (including yourself) for zero benefit – 463035818_is_not_an_ai Jan 19 '18 at 12:50
  • ...nevertheless its is a interesting question from the language-lawyer point of view – 463035818_is_not_an_ai Jan 19 '18 at 12:53
  • @melpomene - yes, that's the order the values would be initialized, but I suspect the OP wants the three values in the order of the initializer-list. Anyway, if you are explicit you get the values you want, and don't have to ask here. :-) – Bo Persson Jan 19 '18 at 12:53
  • 1
    Not sure if there is explicit wording, but uses like [this](https://stackoverflow.com/q/10720377/3002139) strongly imply this is intended to be legal at least. – Baum mit Augen Jan 19 '18 at 13:02
  • 2
    Wow. I just realised that I **rely** on this being well-defined. I hadn't even ever wondered. *[upvotes and crosses fingers]* Note that this answer says there is a sequence point between the initialisation of each member: https://stackoverflow.com/a/2669894/2757035 – underscore_d Jan 19 '18 at 13:07
  • Possible duplicate of [Initializer list \*argument\* evaluation order](https://stackoverflow.com/questions/1589950/initializer-list-argument-evaluation-order) – underscore_d Jan 19 '18 at 13:09
  • @BaummitAugen - I'm not sure the linked question does imply that, because sequence of member initialization is described separately from sequencing of other side effect. It may still _also_ be true, of course. – Useless Jan 19 '18 at 13:09
  • @underscore_d also not quite, because the standard describes sequencing of value computation and side-effects separately too. – Useless Jan 19 '18 at 13:10
  • 1
    @Useless It's not proof of course, but looks like a rather strong hint to me. – Baum mit Augen Jan 19 '18 at 13:10
  • This is not production code, I took it from an example from CppCon. It was designed to showcase the initializer list vs order of declaration initialization order. I took the example and discussed it with someone and the undefined behaviour idea came into discussion. – Keerpich Jan 19 '18 at 19:13

2 Answers2

30

This does not generate Undefined Behavior because:

[class.base.init]#7

[ Note: The initialization performed by each mem-initializer constitutes a full-expression. Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. ]

[intro.execution]

5. A full-expression is

  • [...]
  • an init-declarator or a mem-initializer, including the constituent expressions of the initializer,

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


But beware that:

[class.base.init]#13

In a non-delegating constructor, initialization proceeds in the following order:

  • [...]

  • Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

So your code will effectively assign i + 1 to a, i + 2 to x and i + 3 to b.

Community
  • 1
  • 1
Holt
  • 36,600
  • 7
  • 92
  • 139
  • 1
    Note that this rule is also what makes access to other members work, as in `Wrinkle() : a(rand()), b(a + 1) {}` – Ben Voigt Jan 19 '18 at 19:12
6

The C++17 standard contains an example almost exactly the same as in the question:

struct B1 { B1(int); /* ... */ };
struct B2 { B2(int); /* ... */ };
struct D : B1, B2 {
  D(int);
  B1 b;
  const int c;
};

D::D(int a) : B2(a+1), B1(a+2), c(a+3), b(a+4) { /* ... */ }
D d(10);

This is followed by a note:

[ Note: The initialization performed by each mem-initializer constitutes a full-expression (4.6). Any expression in a mem-initializer is evaluated as part of the full-expression that performs the initialization. — end note ]

Following the link, section 4.6 tells us that one of the definitions of "full-expression" is

... a mem-initializer, including the constituent expressions of the initializer,

The phrase "including constituent expressions of the initiailizer" strongly suggests to me that the above code is legal, because the side effects of ++i will have completed before moving on to the next initializer. This is just my reading of the standard though, I'm happy to defer to anyone with more standardese experience than me.

(It's also worth noting that initialization of members will happen in the order in which they are declared in the class, not in the order in which they appear in the member initializer list).

Tristan Brindle
  • 16,281
  • 4
  • 39
  • 82
  • 8
    *"almost exactly the same as in the question"* - Except that there are no pre-increment on `a` in this example, which is the main point of the question. – Holt Jan 19 '18 at 13:02
  • @Holt now edited, but I see that you've come to the same conclusion in the mean time :) – Tristan Brindle Jan 19 '18 at 13:16