7

[class.mem]/6:

A complete-class context of a class is a
(6.1) function body,
(6.2) default argument,
(6.3) noexcept-specifier ([except.spec]),
(6.4) contract condition, or
(6.5) default member initializer

within the member-specification of the class. [ Note: A complete-class context of a nested class is also a complete-class context of any enclosing class, if the nested class is defined within the member-specification of the enclosing class. — end note ]

The highlighted text above seems to give support to the following snippet:

#include<iostream>
struct A{
    int i = j + 1;
    int j = 1;
};

int main(){
    A a;
    std::cout << a.i << '\n';
    std::cout << a.j << '\n';
}

, and I was expecting it to print

2
1

Both GCC and clang print

1
1

but in addition clang gives the following warning:

prog.cc:3:13: warning: field 'j' is uninitialized when used here [-Wuninitialized]
    int i = j + 1;
            ^
prog.cc:8:7: note: in implicit default constructor for 'A' first required here
    A a;
      ^
prog.cc:2:8: note: during field initialization in the implicit default constructor
struct A{
       ^
1 warning generated.

My assumption is that the code is ill-formed NDR. But why?

WaldB
  • 1,261
  • 7
  • 14

4 Answers4

8

Your code has undefined behavior due to [class.base.init]/9

In a non-delegating constructor, if a given potentially constructed subobject is not designated by a mem-initializer-id (including the case where there is no mem-initializer-list because the constructor has no ctor-initializer), then

  • if the entity is a non-static data member that has a default member initializer ([class.mem]) and either

    [...] the constructor's class is not a union [...]

    the entity is initialized from its default member initializer as specified in [dcl.init];

So, that means

struct A{
    int i = j + 1;
    int j = 1;
};

is translated to

struct A{
    A() : i(j + 1), j(1) {}
    int i;
    int j;
};

and since i is initialized first it uses an uninitialized variable and is undefined behavior.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
2

I think that the code is equal to following:

struct A{
    int i;
    int j;
    A():i(j + 1),j(1){}
};

Which shows that the compilers are right. Because the members are initialized in the order (stated somewhere in the standard*) in which they were declared. The in-place declaration initialization should be just syntactic sugar for their initialization in all ctors. So, the code indeed has undefined behaviour because j is an uninitialized variable.

EDIT: * Found it [10.9.2 Initializing bases and members] (http://eel.is/c++draft/class.base.init)

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

(13.1) First, and only for the constructor of the most derived class ([intro.object]), virtual base classes are initialized in the order they appear on a depth-first left-to-right traversal of the directed acyclic graph of base classes, where “left-to-right” is the order of appearance of the base classes in the derived class base-specifier-list.

(13.2) Then, direct base classes are initialized in declaration order as they appear in the base-specifier-list (regardless of the order of the mem-initializers).

(13.3) 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).

(13.4) Finally, the compound-statement of the constructor body is executed.

Sasha
  • 102
  • 8
Quimby
  • 17,735
  • 4
  • 35
  • 55
  • This has nothing to do with the so called default member initializer (-1). – Alexander Mar 05 '19 at 19:02
  • 1
    @Alexander I do not think I mentioned any default initialization. I am not aware of any (-1) initialization. Non-static primitive variables of fundamental types are not initialized to any value. – Quimby Mar 05 '19 at 19:07
0

The problem here is you use int i = j + 1 before you have declared and initialised j.

What you should have done is

struct A{ int j = 1; int i = j + 1; };

So j should be initialised before i. Hope this helps :)

naynay
  • 25
  • 7
  • The OP understands that. They are asking if the code does not require a diagnostic. – NathanOliver Mar 05 '19 at 18:40
  • @NathanOliver I'm not asking this. I just want to know, based on the standard, why is the code ill-formed NDR. As I mentioned above, the fact that member `i` comes before `j` shouldn't invalidate the code, as a default member initializer is a complete class context as given in [class.mem]/6. – WaldB Mar 05 '19 at 18:45
  • 1
    @WaldB Yes, it is in a completed context, but when the compiler substitutes `j + 1` into the initializer for `i` in the constructor that it creates, `j` is uninitialized. In class member initializers are just syntactic sugar. They don't actually initialize anything. – NathanOliver Mar 05 '19 at 18:47
  • @WaldB The thing is, when `int i = j +1` comes before the initialisation of j, we have something like `int i = 0 + 1` because there is no value for j at the moment. Technically, the code is still valid since it compiles but because it is not in the sequence required to give the result you wanted, you will not get that result. – naynay Mar 05 '19 at 18:50
  • @NathanOliver That's not what [\[class.mem\]/10](http://eel.is/c++draft/class.mem#10) says. – WaldB Mar 05 '19 at 18:53
  • 1
    @WaldB See [\[class.base.init\]/9](https://timsong-cpp.github.io/cppwp/class.base.init#9) – NathanOliver Mar 05 '19 at 19:01
-3

In the example you gave the variable j was not initialized (GCC is perhaps defaulting theses uninitialized integral variables to 0) this why the initial value of i is 1 and hence the printed value By the way most of the time initializing variable this way is not a good idea. Instead use the constructor see this

Soulimane Mammar
  • 1,658
  • 12
  • 20
  • 1
    `for class integral member variables are initialized to 0 by default` No, they are not. Only static variables are implicitly initialized to 0. – Quimby Mar 05 '19 at 19:17