Ambiguous base classes (C++ only)
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.
Name hiding
A
and B
both have a member name
x
. The member name x
of subobject B
hides the member name x
of subobject A
if
A
is a base class of B
. The following example demonstrates this:
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.
Ambiguity and using declarations
C
that inherits from a class
named A
, and x
is a member name
of A
. If you use a using declaration to declare A::x
in C
,
then x
is also a member of C
; C::x
does
not hide A::x
. Therefore using declarations cannot
resolve ambiguities due to inherited members. The following example
demonstrates this: 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
.Unambiguous class members
A
regardless of the number of subobjects
of type A
an object has. The following example demonstrates
this: 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
.Pointer conversions
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
}
- The conversion is not ambiguous. The conversion is ambiguous if multiple instances of the base class are in the derived class.
- A pointer to the derived class can be converted to a pointer to the base class. If this is the case, the base class is said to be accessible.
- Member types must match. For example suppose class
A
is a base class of classB
. You cannot convert a pointer to member ofA
of typeint
to a pointer to member of typeB
of typefloat
. - The base class cannot be virtual.
Overload resolution
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()
.