Reputation: 2774
I accidentally run into the problem having member variables with the same name in classes used in multiple inheritance. My basic idea was that the member variables are simple "merged", i.e. a multiple declaration happens. The compiler did not tell me even a warning, see the MWE below. I understand that it is a bad idea to have variables with the same name, so I think it is at least ambiguous to refer to them in the way I do; so I expected at least a warning or maybe an error.
1) Why the compiler does not write out at least a warning?
2) How handling of those variables is solved internally? (I guess aliases like HW::I and Other::I are used, but how they relate to SW1::I and SW2::I?)
#include <iostream>
struct Other { int I;};
struct HW { int I;};
struct SW1 : Other, HW { int I;};
struct SW2 : HW, Other { int I;};
struct D : SW1 { };
struct E : SW2 { };
int main()
{
E* e = new E;
D* d = new D;
e->I = 3;
SW1* pc1 = dynamic_cast<SW1*>(d);
pc1->I = 2;
std::cerr << d->I;
std::cerr << e->I;
SW2* pc2 = dynamic_cast<SW2*>(e);
pc2->I = 1;
std::cerr << d->I;
std::cerr << e->I;
}
Upvotes: 2
Views: 2093
Reputation: 36597
The compiler is correct not to diagnose any problems with your code. The code, as you've constructed it is not ambiguous. Essentially, a name is ambiguous if it is an equally good match for more than one variable (or class member in your case).
When evaluating e->I
, the first candidate found is the I
that is a member (via inheritance) of class SW2
. The members of I
that SW2
inherits from its base classes are not as good a match as the member defined directly by Sw2
.
Similarly, pc1->I
is unambiguously the member of SW1
, d->I
is the same, and pc2->I
is unambiguously the member of the base class SW2
.
Ambiguity would occur in evaluating e->I
if SW2
did not have its own member named I
(i.e. struct SW2: HW, Other {};
( . In that case, when evaluating e->I
, the name resolution looks in SW2
for a member named I
, and doesn't find it. Resolving the name then considers the two base classes, HW
and Other
, which both have a member named I
. They are equally good matches, so the expression e->I
is ambiguous - and the compiler will issue a diagnostic i.e. an error (not just a warning). In that case, it is possible for the programmer to explicitly resolve the ambiguity using the scope (::
) operator. For example, e->HW::I
or e->Other::I
which fully qualifies the name.
You are right that such multiple usage of a name within a class hierarchy is a bad idea. Both because it can be difficult to correctly resolve the ambiguity in a way that makes sense to a compiler, and because mere mortals will often have trouble following the logic.
Upvotes: 2
Reputation: 170064
Why the compiler does not write out at least a warning?
Because you didn't write anything wrong, dangerous or ambiguous. You or I may be confused, but the compiler has a specific set of lookup rules to handle it.
When you write a class member access expression like e->I
, the compiler doesn't just look up the name I
, it looks up the sub-object that contains the member named this way, along with the member. It also starts with the most derived object type, and looks "up" at base class sub-object until it finds something (this is also how member name hiding in C++ works, in a nutshell).
So for e->I
, it looks for I
in E
. That search finds nothing, so it goes into the base class subject. It find SW2::I
, a name that refers to a unique member defined in SW2
. So it stops.
If there was no SW2::I
, it will continue looking and find both Other::I
and HW::I
. Now the same name is found in two different base class sub-object, and we get an ambiguity. Not a warning of ambiguity, but flat out making the expression e->I
ambiguous, which is an error.
Upvotes: 4
Reputation: 85341
The variables are not merged, you simply get all 3 of them at the same time. You need to cast the pointer to the right type to access the variable you want.
#include <iostream>
struct Other { int I; };
struct HW { int I; };
struct SW1 : public Other, public HW { int I; };
struct D : public SW1 { };
int main() {
D* d = new D;
d->I = 1;
SW1* pc1 = dynamic_cast<SW1*>(d);
pc1->I = 2;
static_cast<Other*>(pc1)->I = 3;
static_cast<HW*>(pc1)->I = 4;
std::cerr << d->I;
std::cerr << static_cast<Other*>(d)->I;
std::cerr << static_cast<HW*>(d)->I;
}
Prints:
234
I.e. the same object d
contains 3 different versions of I
, depending on how you view it.
Upvotes: 1