2

I am doing an old school exam in preparation for my current exam and in one of the questions I am given this code:

#include <iostream>
using namespace std;

struct A
{
  A() : m_a(17) { cout << "A_default" << endl; }
  int m_a;
};

struct B1 : A
{
  B1() : m_b(1) { cout << "B1_default" << endl; }
  B1(const A& a)
  {
    cout << "B1_constructor_with_a_parameter_A" << endl;
    m_b = a.m_a + 1;
  }
  int m_b;
};

struct B2 : A
{
  B2() : m_b(1) { cout << "B2_default" << endl; }
  B2(const B1& b1)
  {
    cout << "B2_constructor_with_a_parameter_B1" << endl;
    m_b = b1.m_b + 100;
  }
  int m_b;
};



int main() 
{
  A a1;
  B1 b1;
  B2 b2 = b1;
  A a2 = b2;
  B1 b3 = b1;
  B1 b4 = b2;
  return 0;
}

This code prints:

A_default
A_default
B1_default
A_default
B2_constructor_with_a_parameter_B1
A_default
B1_constructor_with_a_parameter_A

The task is to match each printed line with the corresponding code line in main. I have gotten all of them right except the last printed line B1_constructor_with_a_parameter_A. The answer sheet says that this printed line comes from the B1 b4 = b2; line in main() but there is no explanation as to why this happens.

So my question is simple. Why does B1 b4 = b2; print B1_constructor_with_a_parameter_A when it doesn't match any constructor pattern in struct B1? Is there some sort of weird object slicing or implicit conversion going on here? That is my best guess. I am a bit confused.

If object slicing or implicit conversion does occur. Why doesn't the line B1 b3 = b1; print anything? I see no difference between B1 b3 = b1; and B1 b4 = b2; except that b2 is of type B2 and b1 is of type B1. Shouldn't the same rules apply since they both inherit A? I am confused.

Waqar
  • 8,558
  • 4
  • 35
  • 43
Schytheron
  • 715
  • 8
  • 28
  • @t.niese Then why doesn't `B1 b3 = b1;` print anything? There seems to be no difference between `B1 b4 = b2;` and `B1 b3 = b1;` except that b2 is of type B2 and b1 is of type B1. Shouldn't the same rules apply? – Schytheron Aug 09 '20 at 12:58
  • 3
    The naming convention used here is garbage. Rename B1 to B and B2 to C, then rename variables of type B to b and C to c. That makes clear the types being used in the expressions, instead of making it a stupid goose hunt. – Yakk - Adam Nevraumont Aug 09 '20 at 13:08
  • What @Yakk-AdamNevraumont said is very true. I had to read it multiple times to be sure that I was correct. If you want to understand code you make it way easier for you if you use proper naming that cannot be confused. – t.niese Aug 09 '20 at 13:11

1 Answers1

3

Why does B1 b4 = b2; print B1_constructor_with_a_parameter_A when it doesn't match any constructor pattern in struct B1?

It does that because b2 is of type B2, which inherits from A, which makes it implicitly convertible to A.

Why doesn't the line B1 b3 = b1; print anything?

Because for this line copy-constructor(which is provided by the compiler) of B1 is invoked since both are of the same type. In the case of B1 = B2, the only matching constructor is B1(const A& a). So, it implicitly converts to type A.

Such behaviour and code is terrible and that's exactly why we should make our constructors explicit. Compile the above code with the following change and you will realize that it doesn't implicitly convert anymore:

explicit B1(const A& a) { cout << "B1_constructor_with_a_parameter_A" << endl; }
Waqar
  • 8,558
  • 4
  • 35
  • 43
  • Soo... as I've understood it, copy-constructors always take precedence over normal constructors and that's why `B1 b3 = b1;` matches with the copy-constructor before (potentially) matching with the `B1(const A& a)` constructor... or would it not match with that constructor regardless (even if the copy-constructor was "unavailable")? – Schytheron Aug 09 '20 at 13:17
  • No, There is no precedence. Suppose you have an object of type `A a1`, and you create another object of type `A` like: `A a2 = a1`, copy constructor will be called. When the types are same, copy constructor is used to construct the object. – Waqar Aug 09 '20 at 13:23
  • And yes, even if you delete the copy constructor, `B1(const A& a)` will not be called. – Waqar Aug 09 '20 at 13:24