1

The following code

#include <iostream>

using namespace std;

class A {};

class B : public A {};

class C : public B {};

void foo(A *a) {
    cout << 'A' << endl;
}

void foo(B *b) {
    cout << 'B' << endl;
}

int main() {
    C *c = new C[10];

    foo(c);
}

compiles fine and prints 'B' as expected.

But when I change the main() function to

int main() {
    C c[10];

    foo(c);
}

I get a compiler error saying

test_arr.cpp: In function 'int main()':
test_arr.cpp:23:10: error: call of overloaded 'foo(C [10])' is ambiguous
test_arr.cpp:23:10: note: candidates are:
test_arr.cpp:11:6: note: void foo(A*)
test_arr.cpp:15:6: note: void foo(B*)

Why is it ambiguous now? I thought an array was always just a pointer so I don't really see the difference.

Edit:

I just realized the whole code I posted was a bad idea to start with: As soon as you add data members to the classes and access them in the foo functions you will run into problems, as explained here, since the foo(B*) function has no way of knowing it's actually dealing with C objects which potentially take more memory space. So don't do this!

Nevertheless I am still interested in understanding why the compiler complains about ambiguity here, since I really don't see a problem with that here.

Community
  • 1
  • 1
PieterNuyts
  • 496
  • 5
  • 20
  • on vs2008, I cannot reproduce the error - and the latter test case prints 'B'. – Gombat Sep 11 '15 at 11:48
  • Does foo(&c[0]) work? – Gombat Sep 11 '15 at 11:49
  • 4
    [Arrays are not pointers](https://stackoverflow.com/questions/1641957/is-array-name-a-pointer-in-c). Where does this idea come from, anyway? Also, the pointer arithmetic you need for array access isn't going to work out when you cast an array of `C` to pointer to `B` (or `A`, for that matter); once `C` contains some data members, `b[1]` will not give you a valid object because `sizeof(B) != sizeof(C)` (and it's undefined behavior before then, as well). – Wintermute Sep 11 '15 at 11:52
  • in foo(B*), you can know, whether it is a C by using dynamic_cast. C* c = dynamic_cast(b) in foo(B* b) and check if c is nullptr (or NULL für c++0x). If it is not nullptr, it is C. – Gombat Sep 11 '15 at 12:08
  • 1
    @Gombat These classes are not polymorphic, you can't use `dynamic_cast`. – Barry Sep 11 '15 at 12:12
  • @Barry, oh you are right. – Gombat Sep 11 '15 at 12:18
  • @Wintermute: Indeed, after posting I also figured out that this code is never going to work; thanks for pointing it out. – PieterNuyts Sep 11 '15 at 12:39

2 Answers2

5

I believe this is a gcc bug. The code compiles on clang.

First, both candidates are viable. From [conv]:

A standard conversion sequence is a sequence of standard conversions in the following order:
— Zero or one conversion from the following set: lvalue-to-rvalue conversion, array-to-pointer conversion, and function-to-pointer conversion.
— Zero or one conversion from the following set: integral promotions, floating point promotion, integral conversions, floating point conversions, floating-integral conversions, pointer conversions, pointer to member conversions, and boolean conversions.

Both calls to foo would involve an array-to-pointer conversion (from C[10] to C*) and then a pointer conversion (from C* to either A* or B*). That is an allowable standard conversion.

Now, how do we rank those conversions? Amusingly, the line from the standard matches exactly our use-case, in [over.ics.rank]:

Two conversion sequences with the same rank are indistinguishable unless one of the following rules applies:
— If class B is derived directly or indirectly from class A and class C is derived directly or indirectly from B,
   — conversion of C* to B* is better than conversion of C* to A*

We have two conversions sequences of the same rank (Conversion), that are both viable, but one is considered better than the other. The code should unambiguously prefer foo(B* ) over foo(A* ). The fact that c is declared an array should not make it ambiguous.

Barry
  • 286,269
  • 29
  • 621
  • 977
1

C c[10] is not a polymorphic type. You might be able to get a pointer to the first item in the array, but that is still just a C.

You would get slicing if you tried to cast it to B.

C* c = new C(); can be be dynamic_cast to B.

Tony The Lion
  • 61,704
  • 67
  • 242
  • 415
  • @sehe I got confused with how this question is related to conversions more than the fact that its `C[10]` is not really a polymorphic type? – Tony The Lion Sep 12 '15 at 00:11