This was an interview question. Consider the following:
struct A {};
struct B : A {};
A a;
B b;
a = b;
b = a;
Why does b = a;
If I am getting interviewed then, I will explain in little philosophical way.
a = b;
is valid, because every B
contains A
as its part. So a
can extract A
from within B
. However, A
doesn't contain B
. thus b
cannot find B
from within A
; that's why,
b = a;
is invalid.
[Analogically, a void*
can be found in any Type*
, but Type*
cannot be found in void*
(thus we need a cast).]
Because the implicitly declared copy assignment operator of B
hides the implicitly declared copy assignment operator of A
.
So for the line b = a
, only the the operator=
of B
is a candidate. But its parameter has type B const&
, which cannot be initialized by an A
argument (you would need a downcast). So you get an error.
Because every B is an A, but not every A is a B.
Edited following comments to make things a bit clearer (I modified your example):
struct A {int someInt;};
struct B : A {int anotherInt};
A a;
B b;
/* Compiler thinks: B inherits from A, so I'm going to create
a new A from b, stripping B-specific fields. Then, I assign it to a.
Let's do this!
*/
a = b;
/* Compiler thinks: I'm missing some information here! If I create a new B
from a, what do I put in b.anotherInt?
Let's not do this!
*/
b = a;
In your example, there's no attributes someInt
nor anotherInt
, so it could work. But the compiler will not allow it anyway.
It's true that a B
is an A
, but an A
is not a B
, but this fact is only directly applicable when you're working with pointers or references to A
's and B
's. The problem here is your assignment operator.
struct A {};
struct B : A {};
Is equivalent to
struct A {
A& operator=(const A&);
};
struct B : A {
B& operator=(const B&);
};
So when you're assigning below:
A a;
B b;
a = b;
The assignment operator on a
can be called with an argument of b
, because a B
is an A
, so b
can be passed to the assignment operator as an A&
. Note that a
's assignment operator only knows about the data that's in an A
, and not the stuff in a B
, so any members of B that aren't part of A get lost - this is known as 'slicing'.
But when you're trying to assign:
b = a;
a
is of type A
, which is not a B
, so a
can't match the B&
parameter to b
's assignment operator.
You would think that b=a
should just call the inherited A& A::operator=(const A&)
, but this is not the case. The assignment operator B& B::operator=(const B&)
hides the operator that would be inherited from A
. It can be restored again with a using A::operator=;
declaration.
Remember that if there's not an explicitly declared copy-assignment operators one will be implicitly declared and defined for any class (and structs are classes in C++).
For struct A
it'll have the following signature:
A& A::operator=(const A&)
And it simply performs memberwise assignment of its subobjects.
a = b;
is OK because B
will match with the const A&
parameter for A::operator=(const A&)
. Since only members of A
are 'memberwise assigned' to the target, any members of B
that aren't part of A
get lost - this is known as 'slicing'.
For struct B
the implcit assignment operator will have the following signature:
B& B::operator=(const B&)
b = a;
is not OK because A
won't match the const B&
argument.
I've changed the names of your structs to make the reason obvious:
struct Animal {};
struct Bear : Animal {};
Animal a;
Bear b;
a = b; // line 1
b = a; // line 2
Clearly, any Bear is also an Animal, but not every Animal can be considered a Bear.
Because every B "isa" A, any instance of B must also be an instance of A: by definition it has the same members in the same order as any other instance of A. Copying b into a loses the B-specific members, but completely fills the members of a resulting in a struct that satisfies the requirements of A. Copying a to b, on the other hand, may leave b incomplete because B could have more members than A. That's hard to see here because neither A nor B has any members at all, but this is why the compiler allows one assignment and not the other.