Name Ambiguities

Multiple inheritance introduces the possibility for names to be inherited along more than one path. The class-member names along these paths are not necessarily unique. These name conflicts are called “ambiguities.”

Any expression that refers to a class member must make an unambiguous reference. The following example shows how ambiguities develop:

// Declare two base classes, A and B.

class A

{

public:

unsigned a;

unsigned b();

};

class B

{

public:

unsigned a(); // Note that class A also has a member “a”

int b(); // and a member “b”.

char c;

};

// Define class C as derived from A and B.

class C : public A, public B

{

};

Given the preceding class declarations, code such as the following is ambiguous:

C *pc = new C;

pc->b();

Consider the preceding example. Because the name a is a member of both class A and class B, the compiler cannot discern which a designates the function to be called. Access to a member is ambiguous if it can refer to more than one function, object, type, or enumerator.

The compiler detects ambiguities by performing tests in this order:

1.If access to the name is ambiguous (as just described), an error message is generated.

2.If overloaded functions are unambiguous, they are resolved. (For more information about function overloading ambiguity, see “Argument Matching” in Chapter 12, on topic .)

3.If access to the name violates member-access permission, an error message is generated. (For more information, see Chapter 10, “Member-Access Control”.)

When an expression produces an ambiguity through inheritance, you can manually resolve it by qualifying the name in question with its class name. To make the preceding example compile properly with no ambiguities, use code such as:

C *pc = new C;

pc->B::a();

Note:

The potential ambiguity introduced by the class declarations of classes A and B in the example shown on the previous page does not cause an error; the ambiguous accessing of a member causes the error.

Ambiguities and Virtual Base Classes

If virtual base classes are used, functions, objects, types, and enumerators can be reached through multiple-inheritance paths. Because there is only one instance of the base class, there is no ambiguity when accessing these names.

Figure 9.10 shows how objects are composed using virtual and nonvirtual inheritance.

In Figure 9.10, accessing any member of class A through nonvirtual base classes causes an ambiguity; the compiler has no information that explains whether to use the subobject associated with B or the subobject associated with C. However, when A is specified as a virtual base class, there is no question which subobject is being accessed.

Dominance

It is possible for more than one name (function, object, or enumerator) to be reached through an inheritance graph. Such cases are considered ambiguous with nonvirtual base classes. They are also ambiguous with virtual base classes, unless one of the names “dominates” the others.

A name dominates another name if it is defined in both classes, and one class is derived from the other. The dominant name is the name in the derived class; this name is used when an ambiguity would otherwise have arisen, as shown in the following example:

class A

{

public:

int a;

};

class B : public virtual A

{

public:

int a();

};

class C : public virtual A

{

...

};

class D : public B, public C

{

public:

D() { a(); } // Not ambiguous. B::a() dominates A::a.

};

Ambiguous Conversions

Explicit and implicit conversions from pointers or references to class types can cause ambiguities. Figure 9.11 shows the following:

The declaration of an object of type D.

The effect of applying the address-of operator (&) to that object. Note that the address-of operator always supplies the base address of the object.

The effect of explicitly converting the pointer obtained using the address-of operator to the base-class type A. Note that coercing the address of the object to type A* does not always provide the compiler with enough information as to which subobject of type A to select; in this case, two subobjects exist.

The conversion to type A* (pointer to A) is ambiguous because there is no way to discern which subobject of type A is the correct one. Note that you can avoid the ambiguity by explicitly specifying which subobject you mean to use, as follows:

(A *)(B *)&d // Use B subobject.

(A *)(C *)&d // Use C subobject.