5

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.

27
  • 6
    It works exactly as the output says - first the B2 subobject is (default-)initialized, then the B1 constructor is passed 10. The B2 is initialized first because it is mentioned first in the list of base classes.
    – molbdnilo
    Commented Jul 10 at 8:01
  • 3
    That is, it behaves pretty much as if D had the constructor D(int x): B2(), B1(x) {}.
    – molbdnilo
    Commented Jul 10 at 8:09
  • 3
    So how member B1::i is initialized before B2 subobject ? Why do you think this happens? From your exposed output, the B2::B2 is called first, as it should. Commented Jul 10 at 8:15
  • 3
    @mada Again: why do you believe i is initialized first? There is nothing in the output to suggest that it is.
    – molbdnilo
    Commented Jul 10 at 8:23
  • 2
    if you do not explcitily call the constructor of a base, then it is initialized by a default constructor. You do not explicitly call B2 constructor. Perhaps removing the argument, and turn it into B2(): j(7) { print("B2 constructor"); } will help to clarify your misunderstanding. That is effectively what is called Commented Jul 10 at 8:48

3 Answers 3

6

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)
  {
  }
  
};
3
  • Thanks for your answer. Something relatively irrelevant: In the given quote: "... if the base class subobject were to be initialized as part of the D object ..." - Why if here? I mean, is there a case where the base subobject is not initialized as part of derived D?
    – mada
    Commented Jul 10 at 9:06
  • @mada There may be some edge case. But I'm not seeing any edge case possible here. Maybe a new question for this follow up question would be better. You're welcome. Maybe someone else will answer that. Commented Jul 10 at 9:10
  • @mada Virtual inheritance. Commented Jul 10 at 9:11
0

This is expected. Note that to construct D you have to call constructors for all ancestors, so this means B1 and B2. Order of invoking those constructors is enforced by order of inheritance list. Not this is old C++ rule which has to be maintained forever.

So when D is constructed default constructor for B2 is called then constructor for B1 is called with argument you have passed to it.

Better demo:

#include <print>
#include <source_location>

void log(int val, const std::source_location& from, const std::source_location& loc = std::source_location::current())
{
    std::println("{}:{} val={} invoked from: {}:{}", loc.function_name(), loc.line(), val, from.function_name(), from.line());
}

struct B1 {
    B1(int _i = 5, const std::source_location& loc = std::source_location::current())
        : i(_i)
    {
        log(i, loc);
    };
    int i;
};

struct B2 {
    B2(int _j = 7, const std::source_location& loc = std::source_location::current())
        : j(_j)
    {
        log(j, loc);
    }
    int j;
};

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

int main(void)
{
    D d = D { 10 };
    std::println("B1::i = {}", d.i);
    std::println("B2::j = {}", d.j);
}

Log output:

B2::B2(int, const std::source_location &):22 val=7 invoked from: D::B1(int, const std::source_location &):28
B1::B1(int, const std::source_location &):13 val=10 invoked from: int main():33
B1::i = 10
B2::j = 7

Note that compiler created constructor which is called: D::B1!

0
-2

The output of this program indicates that B1(int) constructor is called first to initialize member i with 10. Then before the body of B1(int) is executed, and I don't know how the execution sequence is jumped to B2(int)

How do you arrive at this conclusion? The output of the program indicates only two things:

  • Constructor B2 ran before constructor B1
  • When you instantiated D{10}, constructor B1 was selected to be passed that argument, via the using expression in D's class body.

I don't know what the standard says specifically about the order of initialization when inheriting from multiple independant base classes (and I don't want to make assumptions here that may be incorrect), but your program works in a way I would expect it to.

2
  • 1
    "I don't know what the standard says specifically..." The question is language-lawyered. So this doesn't actually answer the question. Commented Jul 10 at 8:17
  • The question quotes the relevant section from the standard Commented Jul 10 at 12:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.