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.
Why does
B1 b4 = b2;
print B1_constructor_with_a_parameter_A when it doesn't match any constructor pattern instruct 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; }