52

The following snippet produces an "ambigious call to foo" error during compilation, and I'd like to know if there is any way around this problem without fully qualifying the call to foo:

#include <iostream>

struct Base1{
    void foo(int){
    }
};

struct Base2{
    void foo(float){
    }
};

struct Derived : public Base1, public Base2{
};

int main(){
    Derived d;
    d.foo(5);

    std::cin.get();
    return 0;
}

So, question is as the title says. Ideas? I mean, the following works flawlessly:

#include <iostream>

struct Base{
    void foo(int){
    }
};

struct Derived : public Base{
    void foo(float){
    }
};

int main(){
    Derived d;
    d.foo(5);

    std::cin.get();
    return 0;
}
Wim Coenen
  • 66,094
  • 13
  • 157
  • 251
Xeo
  • 129,499
  • 52
  • 291
  • 397
  • 2
    add logging statements inside the two foo (in the second case) to constant which function is called, you'll be surprised... C++ is full of arcane rule ;) – Matthieu M. Mar 20 '11 at 14:17
  • 1
    @Matthieu: *gasp*! Damn hiding rules. :( – Xeo Mar 20 '11 at 14:27

3 Answers3

52

Member lookup rules are defined in Section 10.2/2

The following steps define the result of name lookup in a class scope, C. First, every declaration for the name in the class and in each of its base class sub-objects is considered. A member name f in one sub-object B hides a member name f in a sub-object A if A is a base class sub-object of B. Any declarations that are so hidden are eliminated from consideration. Each of these declarations that was introduced by a using-declaration is considered to be from each sub-object of C that is of the type containing the declara-tion designated by the using-declaration. If the resulting set of declarations are not all from sub-objects of the same type, or the set has a nonstatic member and includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed. Otherwise that set is the result of the lookup.

class A {
public:
  int f(int);

};
class B {
public:
   int f();

};
class C : public A, public B {};
int main()
{
     C c;
     c.f(); // ambiguous
}

So you can use the using declarations A::f and B::f to resolve that ambiguity

class C : public A, public B {
     using A::f;
     using B::f;

};

int main()
{
     C c;
     c.f(); // fine
}

The second code works flawlessly because void foo(float) is inside C's scope. Actually d.foo(5); calls void foo(float) and not the int version.

Prasoon Saurav
  • 91,295
  • 49
  • 239
  • 345
  • 2
    The `void foo(float)` version being called really got me there.. thanks for your extensive answer. :) – Xeo Mar 20 '11 at 14:31
  • 1
    One thing that comes to mind... is there a situation, where one would want to hide the base class functions if they have different signatures? For same signature functions, sure, that's useful, but for different ones I just can't imagine a good example... – Xeo Mar 21 '11 at 20:21
  • 2
    **If the resulting set of declarations are .. includes members from distinct sub-objects, there is an ambiguity and the program is ill-formed** What is the reason for this rule? Examples of what would otherwise happen would be nice. – Olumide Mar 03 '16 at 22:29
9

Name lookup is a separate phase to overload resolution.

Name lookup occurs first. That is the process of deciding which scope the name applies to. In this case we must decide whether d.foo means d.D::foo, or d.B1::foo, or d.B2::foo. The name lookup rules do not take into account function parameters or anything; it is purely about names and scopes.

Only once that decision has been made, do we then perform overload resolution on the different overloads of the function in the scope where the name was found.

In your example, calling d.foo() would find D::foo() if there were such a function. But there is none. So, working backwards up the scopes, it tries the base classes. Now foo could equally look up to B1::foo or B2::foo so it is ambiguous.

For the same reason, you would get ambiguity calling unqualified foo(5); inside a D member function.


The effect of the recommended solution:

struct Derived : public Base1, public Base2{
    using Base1::foo;
    using Base2::foo;

is that this creates the name D::foo, and makes it identify two functions. The result is that d.foo resolves to d.D::foo, and then overload resolution can happen on these two functions that are identified by D::foo.

Note: In this example D::foo(int) and Base1::foo(int) are two identifiers for the one function; but in general, for the name lookup and overload resolution process, it doesn't make a difference whether they are two separate functions or not.

M.M
  • 138,810
  • 21
  • 208
  • 365
2

Will it work for you?

struct Derived : public Base1, public Base2{
   using Base2::foo;}
Taryn
  • 242,637
  • 56
  • 362
  • 405
a1ex07
  • 36,826
  • 12
  • 90
  • 103