Why do multiple-inherited functions with same name but different signatures not get treated as overloaded functions?
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;
}
Solution 1:
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 namef
in one sub-objectB
hides a member namef
in a sub-objectA
ifA
is a base class sub-object ofB
. 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 ofC
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.
Solution 2:
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.
Solution 3:
Will it work for you?
struct Derived : public Base1, public Base2{
using Base2::foo;}