When you derive classes, ambiguities can result if base and derived classes have members with the same names. Access to a base class member is ambiguous if you use a name or qualified name that does not refer to a unique function or object. The declaration of a member with an ambiguous name in a derived class is not an error. The ambiguity is only flagged as an error if you use the ambiguous member name.
For example, suppose that two classes named A and B both have a member named x, and a class named C inherits from both A and B. An attempt to access x from class C would be ambiguous. You can resolve ambiguity by qualifying a member with its class name using the scope resolution (::) operator.
CCNX14G
class B1 {
public:
int i;
int j;
void g(int) { }
};
class B2 {
public:
int j;
void g() { }
};
class D : public B1, public B2 {
public:
int i;
};
int main() {
D dobj;
D *dptr = &dobj;
dptr->i = 5;
// dptr->j = 10;
dptr->B1::j = 10;
// dobj.g();
dobj.B2::g();
}
The statement dptr->j = 10 is ambiguous because the name j appears both in B1 and B2. The statement dobj.g() is ambiguous because the name g appears both in B1 and B2, even though B1::g(int) and B2::g() have different parameters.
The compiler checks for ambiguities at compile time. Because ambiguity checking occurs before access control or type checking, ambiguities may result even if only one of several members with the same name is accessible from the derived class.
struct A {
int x;
};
struct B: A {
int x;
void f() { x = 0; }
};
int main() {
B b;
b.f();
}
The assignment x
= 0 in function B::f() is not ambiguous
because the declaration B::x has hidden A::x.struct A { int x; };
struct B { int y; };
struct C: A, virtual B { };
struct D: A, virtual B {
int x;
int y;
};
struct E: C, D { };
int main() {
E e;
// e.x = 1;
e.y = 2;
}
The assignment e.x = 1 is ambiguous. The declaration D::x hides A::x along the path D::A::x, but it does not hide A::x along the path C::A::x. Therefore the variable x could refer to either D::x or A::x. The assignment e.y = 2 is not ambiguous. The declaration D::y hides B::y along both paths D::B::y and C::B::y because B is a virtual base class.
struct A {
int x;
};
struct B: A { };
struct C: A {
using A::x;
};
struct D: B, C {
void f() { x = 0; }
};
int main() {
D i;
i.f();
}
The compiler will not allow the assignment x
= 0 in function D::f() because it is ambiguous.
The compiler can find x in two ways: as B::x or
as C::x.struct A {
int x;
static int s;
typedef A* Pointer_A;
enum { e };
};
int A::s;
struct B: A { };
struct C: A { };
struct D: B, C {
void f() {
s = 1;
Pointer_A pa;
int i = e;
// x = 1;
}
};
int main() {
D i;
i.f();
}
The compiler allows the assignment s = 1,
the declaration Pointer_A pa, and the statement int
i = e. There is only one static variable s,
only one typedef Pointer_A, and only one enumerator e.
The compiler would not allow the assignment x = 1 because x can
be reached either from class B or class C.class W { /* ... */ };
class X : public W { /* ... */ };
class Y : public W { /* ... */ };
class Z : public X, public Y { /* ... */ };
int main ()
{
Z z;
X* xptr = &z; // valid
Y* yptr = &z; // valid
W* wptr = &z; // error, ambiguous reference to class W
// X's W or Y's W ?
}
You can use virtual base classes to avoid ambiguous reference.
For example: class W { /* ... */ };
class X : public virtual W { /* ... */ };
class Y : public virtual W { /* ... */ };
class Z : public X, public Y { /* ... */ };
int main ()
{
Z z;
X* xptr = &z; // valid
Y* yptr = &z; // valid
W* wptr = &z; // valid, W is virtual therefore only one
// W subobject exists
}
struct A {
int f() { return 1; }
};
struct B {
int f(int arg) { return arg; }
};
struct C: A, B {
int g() { return f(); }
};
The compiler will not allow the function call to f() in C::g() because
the name f has been declared both in A and B.
The compiler detects the ambiguity error before overload resolution
can select the base match A::f().