5

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?

Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • yes but i didnt have access to a C++ compiler – Claudiu Dec 22 '08 at 04:52
  • http://codepad.org/ for gcc and http://www.comeaucomputing.com/tryitout/ for comeau are two good sites for quick testing. – Johannes Schaub - litb Dec 22 '08 at 04:54
  • ah, codepad is what i was looking for, thanks! i was also looking for an explanation on the topic, not just "how does this work if i run it" – Claudiu Dec 22 '08 at 04:58
  • @Claudiu, I added links to the source material for my answer. – Robert Gould Dec 24 '08 at 02:37
  • Had a similar issue with doIt(string a) and doIt(char *a). It's not a question of "just try it out". Even with a "char *" it will call into doIt(string a), so this is a valid question. Came across this because i was calling into a function using a string type, which in turn called a char * version of teh function. However the string input function was recursing indefinitely until it blew up the stack. – Owl Oct 14 '21 at 14:20

6 Answers6

8

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
  • 390,455
  • 97
  • 512
  • 589
5

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)
Paige Ruten
  • 172,675
  • 36
  • 177
  • 197
3

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
  • 496,577
  • 130
  • 894
  • 1,212
  • @litb can you explain what is the difference between static_cast and implcit_cast – yesraaj May 15 '09 at 08:29
  • 2
    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 May 15 '09 at 10:36
2

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
  • 68,773
  • 61
  • 187
  • 272
1

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());

Evan Teran
  • 87,561
  • 32
  • 179
  • 238
user20844
  • 6,527
  • 4
  • 18
  • 9
0

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
  • 88,451
  • 51
  • 221
  • 321