Are functions calls in a constructor's initializer-list sequenced?
S

2

34

Consider:

int f () {
    static int i = 0;
    return i++;
}

struct Test {
    int a, b;
    Test () : a(f()), b(f()) {}
};

Test t;

I know that a is initialized before b due to the order of their declaration in the struct.

I also know that the two calls to f in g(f(), f()) are unsequenced.

So I am wondering if it is guaranteed that t.a == 0 and t.b == 1?

Sclerosis answered 18/4, 2017 at 18:39 Comment(9)
@FrançoisAndrieux I don't think it's a duplicate. This question is specifically dealing with the ordering of function calls in a member initialization list, which isn't what that question deals with.Gwinn
@FrançoisAndrieux - don't think it's a dup. OP knows that a is initialised before b. But is asking if the two calls to f() are sequenced or not. It could be that f() is called twice before a or b is initialised.Connell
I need to verify but I believe they are.Rutland
If the order was not the same as the initialization order, you couldn't use previously initialized members in subsequent initializations.Thirion
Are you sure g(f(), f()) is undefined behavior, I thought it could merely evaluate the two calls to f in an unsequenced manner, it still has to pick one way or the other. I dont think there are any nasal demons here.Cancer
@Cancer I think you are correct, do you know what the correct terminology is to refer to this behaviour?Sclerosis
@Sclerosis the standard refers to "unsequenced" operations in several places like this. It used to refer to "sequence points" which were any construct that controlled the order an expression was evaluated, and unsequenced code as code not controlled in its order by sequence points. Now the standard I believe removed mention of sequence points and just refers to statements like "happens after" or "happens before". But unsequenced is still used for code with no guarantee of order.Cancer
@Cancer Edited.Sclerosis
@Sclerosis Awesome, thanks, +1Cancer
G
32

So I am wondering if it is guaranteed that t.a == 0 and t.b == 1?

This will always be true so long as a comes before b in the class declaration and nothing else calls f() between the initialization of a and b. Class members are initialized in the order they are declared in the class. [class.base.init]/11:

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 since a comes before b then when the constructor initializes a it will call f() the first time and then it will call it a second time when it initializes b.

We also know there is a sequence point between member initializer because [class.base.init]/7:

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

tells us each initializer is a full expression and each full expression is sequenced: [intro.execution]/14

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.

Greaten answered 18/4, 2017 at 19:1 Comment(2)
Brilliant answer ! But of course, only if f() is called nowhere else and especially in no other static initializer :-)Dominions
@Dominions Thanks. I added that little tidbit as well.Greaten
R
6

I know that a is initialized before b due to the order of their declaration in the struct.

That's true.

My interpretation of that constraint is that a cannot be initialized before b unless the evaluation of the initializer expression is complete before b is initialized.

I don't see anything in the standard that speaks of sequencing the evaluation of the expressions used to initialize non-static members. However, I see the following example in the C++11 Standard (12.6.2/12):

Names in the expression-list or braced-init-list of a mem-initializer are evaluated in the scope of the constructor for which the mem-initializer is specified. [ Example:

class X {
  int a;
  int b;
  int i;
  int j;
  public:
  const int& r;
  X(int i): r(a), b(i), i(i), j(this->i) { }
};

That won't be valid unless the evaluation of this->i is sequenced after i is initialized.

Rutland answered 18/4, 2017 at 18:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.