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?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Winestone
  • 1,500
  • 9
  • 19
  • @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. – Xirema Apr 18 '17 at 18:42
  • @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. – Richard Critten Apr 18 '17 at 18:42
  • 1
    I need to verify but I believe they are. – R Sahu Apr 18 '17 at 18:44
  • 1
    If the order was not the same as the initialization order, you couldn't use previously initialized members in subsequent initializations. – François Andrieux Apr 18 '17 at 18:45
  • 3
    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. – Vality Apr 18 '17 at 19:57
  • @Vality I think you are correct, do you know what the correct terminology is to refer to this behaviour? – Winestone Apr 18 '17 at 20:35
  • @Winestone 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. – Vality Apr 18 '17 at 20:39
  • @Vality Edited. – Winestone Apr 18 '17 at 20:50
  • @Winestone Awesome, thanks, +1 – Vality Apr 18 '17 at 20:52

2 Answers2

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.

Community
  • 1
  • 1
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
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.

R Sahu
  • 204,454
  • 14
  • 159
  • 270