Does incrementing in a member initializer list generate undefined behavior?
Asked Answered
A

2

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.

Anette answered 19/1, 2018 at 12:41 Comment(13)
What are your expected values for a, b and x if you are doing Wrinkle tmp{1}?Jenninejennings
Wrinkle(int i) : a(i+1), b(i+2), x(i+3) {}Moye
@BoPersson That looks like the wrong order. I'd expect a, x, b.Hedrick
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 benefitMazuma
...nevertheless its is a interesting question from the language-lawyer point of viewMazuma
@Hedrick - 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. :-)Moye
Not sure if there is explicit wording, but uses like this strongly imply this is intended to be legal at least.Greasy
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://mcmap.net/q/338126/-in-what-order-are-non-static-data-members-initializedIronwork
Possible duplicate of Initializer list *argument* evaluation orderIronwork
@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.Greek
@Ironwork also not quite, because the standard describes sequencing of value computation and side-effects separately too.Greek
@Greek It's not proof of course, but looks like a rather strong hint to me.Greasy
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.Anette
G
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.

Guff answered 19/1, 2018 at 13:10 Comment(1)
Note that this rule is also what makes access to other members work, as in Wrinkle() : a(rand()), b(a + 1) {}Bollard
A
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).

Amitie answered 19/1, 2018 at 13:0 Comment(2)
"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.Guff
@Guff now edited, but I see that you've come to the same conclusion in the mean time :)Amitie

© 2022 - 2024 — McMap. All rights reserved.