views:

841

answers:

6

Say I have three classes:

class X{};
class Y{};
class Both : public X, public Y {};

I mean to say I have two classes, and then a third class which extends both (multiple-inheritance).

Now say I have a function defined in another class:

void doIt(X *arg) { }
void doIt(Y *arg) { }

and I call this function with an instance of both:

doIt(new Both());

This causes a compile-time error, stating that the function call is ambiguous.

What are the cases, besides this one, where the C++ compiler decides the call is ambiguous and throws an error, if any? How does the compiler determine what these cases are?

+5  A: 

I get this error with gcc:

jeremy@jeremy-desktop:~/Desktop$ g++ -o test test.cpp
test.cpp: In function ‘int main(int, char**)’:
test.cpp:18: error: call of overloaded ‘doIt(Both&)’ is ambiguous
test.cpp:7: note: candidates are: void doIt(X)
test.cpp:11: note:                 void doIt(Y)
yjerem
+7  A: 

Simple: if it's ambiguous, then the compiler gives you an error, forcing you to choose. In your snippet, you'll get a different error, because the type of new Both() is a pointer to Both, whereas both overloads of doIt() accept their parameters by value (i.e. they do not accept pointers). If you changed doIt() to take arguments of types X* and Y* respectively, the compiler would give you an error about the ambiguous function call.

If you want to explicitly call one or the other, you cast the arguments appropriately:

void doIt(X *arg) { }
void doIt(Y *arg) { }
Both *both = new Both;
doIt((X*)both);  // calls doIt(X*)
doIt((Y*)both);  // calls doIt(Y*)
delete both;
Adam Rosenfield
+1 for proper memory management =P.
Claudiu
+1 despite the use of C style cast (and manual memory management)
Motti
A: 

AFAIK the C++ compiler will always pick the closest and most specific match that it can determine at compile time.

However, if your object is derived from both, I think that the compiler should give you an error or at least a very severe warning. Declaration order should not matter since this is about subtyping relations, and the first object is not "more of a subtype" than the other.

Uri
+1  A: 

you need to explicitly cast your argument to either x or y

doIt(new Both());

so add...

(X *) or (Y *)

like...

doIt((X *)new Both());

You mean (X *) and (Y *), i'll fix that for you
Evan Teran
Also, you should favor c++ style casts
Evan Teran
+1  A: 

The compiler does a depth-search, not a breadth-search for picking overloads. The full answer is in Herb Sutter's exceptional C++, unfortunately I don't have the book in hand.

Edit: Got the book at hand now It's called the depth first rule is called "The Interface Principle":

The Interface Principle For a class X, all functions, including free functions, that both (a) "mention" X, and (b) are "supplied with" X are logically part of X, because they form part of the interface of X.

but there is a secondary rule called the "Koenig Lookup", that makes things harder.

Quote:"(simplified): if you supply a function argument of class type (here x, of type A::X), then to look up the correct function name the compiler considers matching names in the namespace (here A) containing the argument's type" -Herb Sutter, Exceptional C++, p120

Robert Gould
can anyone expand on this?
Claudiu
+2  A: 

This is a perfect example of using boost::implicit_cast:

void doIt(X *arg) { }
void doIt(Y *arg) { }

doIt(boost::implicit_cast<X*>(new Both));

Unlike with other solutions (including static_cast), the cast will fail if no implicit conversion from Both* to X* is possible. This is done by a trick, best shown at a simple example:

X * implicit_conversion(X *b) { return b; }

That's what is boost::implicit_cast, just that it is a template which tells it the type of b.

Johannes Schaub - litb
@litb can you explain what is the difference between static_cast and implcit_cast
yesraaj
you can down-cast with static_cast. not so with implicit_cast. static_cast basically allows you to do any implicit conversion, and in addition the reverse of any implicit conversion (up to some limits. you can't downcast if there is a virtual base-class involved). But implicit_cast will *only* accept implicit conversions. no down-cast, no void*->T*, no U->T if T has only explicit constructors for U.
Johannes Schaub - litb