c++inheritanceconstructorlanguage-lawyerinheriting-constructors

Initialization order of inherited constructors


I have this example:

#include <iostream>
#define print(X) std::cout << X << std::endl

struct B1 {
    B1(int _i = 5): i(_i) { print("B1 constructor"); };
    int i;
};

struct B2 {
    B2(int _j = 7): j(_j) { print("B2 constructor"); }
    int j;
};

struct D : B2, B1 {
    using B1::B1;
};

int main(void)
{
    D d = D{10};
    print("B1::i = " << d.i);
    print("B2::j = " << d.j);
}

The output of this program is:

B2 constructor
B1 constructor
B1::i = 10
B2::j = 7

Per §11.9.4[class.inhctor.init]/1:

When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited ([namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the inherited constructor if the base class subobject were to be initialized as part of the D object ([class.base.init]). The invocation of the inherited constructor, including the evaluation of any arguments, is omitted if the B subobject is not to be initialized as part of the D object. The complete initialization is considered to be a single function call; in particular, unless omitted, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the D object.

Firstly, per §11.9.4/1, since D inherits constructor B1(int) from base B1, that inherited constructor can initialize subobject B1; further, the parameter _i is fully initialized before initializing any part of D, hence the inherited constructor D::B1(int) is selected by overload resolution which initializes B1::i members by mem-initializer-list.

Since B2 constructor is printed first, this means that B2 constructor is called before B1. So how member B2::j is initialized with default argument 7 not value 10 that's passed in the call D{10}?

I'm not sure whether that happened, but I can't understand the sequence of execution in this case. and where this behavior is necessary in the standard.


Solution

  • but I can't understand the sequence of execution

    The important thing here is that the "only" the inherited ctor(B1::B1() here) will use the D's ctor's parameter to initialize its data member.

    This here means that since you've inherited B1's ctor, the parameter _i in the ctor D::D(int _i) will be passed as an argument to initialize i of the B1 subobject.

    Baiscally, your code class' D is equivalent to writing:

    struct D : public B2, public B1
    {
      inline D(int _i) 
      : B2(7)
    //--vvvvvv------------->this is because you inherited B1's ctor
      , B1(_i)
      {
      }
      
    };
    

    This is given in your quoted class.inhctor.init only which has been highlighted:

    When a constructor for type B is invoked to initialize an object of a different type D (that is, when the constructor was inherited ([namespace.udecl])), initialization proceeds as if a defaulted default constructor were used to initialize the D object and each base class subobject from which the constructor was inherited, except that the B subobject is initialized by the inherited constructor if the base class subobject were to be initialized as part of the D object ([class.base.init]). The invocation of the inherited constructor, including the evaluation of any arguments, is omitted if the B subobject is not to be initialized as part of the D object.The complete initialization is considered to be a single function call; in particular, unless omitted, the initialization of the inherited constructor's parameters is sequenced before the initialization of any part of the D object.

    (emphasis mine)


    You can additionally verify this by inheriting B2's ctor by doing using B2::B2. Then your class D will be equivalent to:

    struct D : public B2, public B1
    {
      inline D(int _j)
    //--vvvvvv------------>this happens when you inherit B2's ctor
      : B2(_j)
      , B1(5)
      {
      }
      
    };