katang
katang

Reputation: 2774

Multiple inheritance with the same variable name in the classes

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

Answers (3)

Peter
Peter

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

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

rustyx
rustyx

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

Related Questions