12

This is the n-th question about this, but I couldn't find exact duplicate...

Suppose the following code:

#include <iostream>

struct S {
    int x;
    int y;
};

class C {
public:
    S s;
    C() : s{123, s.x} {}
};

int main() {
     std::cout << C().s.y << '\n';
}

Is it OK to initialize s.y like this? (only JetBrains' ReSharper complains about it with the following: Object member this->s.x might not be initialized).

It would be great if someone confirms their answer with a quote from the standard.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
Kiril Kirov
  • 37,467
  • 22
  • 115
  • 187
  • The [closest rule](http://eel.is/c++draft/special#class.cdtor-1) I know against this kind of thing doesn't apply here, because `S` is trivial. Again, [this rule](http://eel.is/c++draft/dcl.init.aggr#3) seem to favor you. – WhiZTiM Apr 05 '17 at 14:59
  • Related [Is it defined behavior to reference an early member from a later member expression during aggregate initialization?](http://stackoverflow.com/q/32940847/1708801) – Shafik Yaghmour Apr 08 '17 at 20:48

2 Answers2

1

From C++14

8.5.1 Aggregates [dcl.init.aggr]

1 An aggregate is an array or a class (Clause 9) with no user-provided constructors (12.1), no private or protected non-static data members (Clause 11), no base classes (Clause 10), and no virtual functions (10.3).

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

This means that s.x is first initialized with 123, then s.y is initialized with s.x.

Without optimization, GCC 6.3 generates

C::C():
        push    rbp
        mov     rbp, rsp
        mov     QWORD PTR [rbp-8], rdi
        mov     rax, QWORD PTR [rbp-8] # read address of s
        mov     DWORD PTR [rax], 123   # write 123 to s.x (offset 0 from s)
        mov     rax, QWORD PTR [rbp-8] # read address of s again
        mov     edx, DWORD PTR [rax]   # read contents of s.x to edx
        mov     rax, QWORD PTR [rbp-8] # read address of s
        mov     DWORD PTR [rax+4], edx # write s.y (offset 4 from s)
        nop
        pop     rbp
        ret

Which agrees with what the standards says.

Paul Floyd
  • 5,530
  • 5
  • 29
  • 43
  • And what about with optimizations? – WhiZTiM Apr 05 '17 at 15:17
  • With optimizations, most of the code gets removed including s.y and the result is pretty much "std::cout << 123 << '\n'; – Paul Floyd Apr 05 '17 at 15:20
  • 1
    That they are *taken* as initialisers doesn't necessarily mean that the initialisation is also performed in the same order. What this part of the standard says is that the compiler must respect the order of `x` and `y` as they appear in the struct's definition ("in increasing member order"). What a compiler actually does with the code is not a suitable proof for any language-lawyer question. If the standard actually mandates a specific order of initialisation, then it must say so elsewhere. – Christian Hackl Apr 05 '17 at 15:31
  • I can't quite understand this rule. Does "taken" mean that the elements are both evaluated & used as an initializers? it would seem so – Ap31 Apr 05 '17 at 15:54
  • My understanding is that the member fields are copy-initialized from the init list element expressions. – Paul Floyd Apr 05 '17 at 17:46
1

While it would seem that there is no rule that explicitly states that this trick is ill-formed, it is not enough for it to have a well-defined behavior.

I think it has some issues with order of evaluation:

this rule defines the order of evaluation for the expressions in the braced list; Of course, there is an order for member initialization too.

It is safe to say that every struct member is initialized after the evaluation of the corresponding expression in the bracket list (obviously s.x in the braced list is evaluated before initializing s.y).

However there seem to be no rule that would state that s.x in your case has to be initialized before evaluating the second element of braced list, e.g. the program could evaluate all the expressions in the bracket list before even starting initializing the struct fields.

Of course, the absence of a rule is not easy to prove, but if it's not there, it looks like UB.

UPD: the rule from @PaulFloyd's answer does indeed closely resemble what was missing in my answer, perhaps it's not a UB after all.

Ap31
  • 3,244
  • 1
  • 18
  • 25