为什么具有相同名称但不同签名的多重继承函数不能被视为重载函数?

52
以下代码片段在编译时会产生 "ambigious call to foo" 错误,我想知道是否有任何方法可以解决这个问题,而不必完全限定对 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;
}

所以,问题就像标题所说的一样。有什么想法吗?我的意思是,以下内容可以完美运行:

#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;
}

2
在第二个案例中,在两个foo函数内添加日志记录语句,以便了解调用哪个函数,你会感到惊讶的... C++充满了神秘的规则 ;) - Matthieu M.
1
@Matthieu:喘气!该死的隐藏规则。:( - Xeo
3个回答

52

成员查找规则定义在第10.2 / 2节中。

  

以下步骤定义了在类作用域中名称查找的结果。首先,考虑到该类及其每个基类子对象中名称的每个声明。如果一个子对象中的成员名称隐藏了子对象中的成员名称,则的基类子对象。 任何隐藏的声明都将被从考虑中排除。通过using-declaration引入的每个此类声明都被认为来自于包含using-declaration指定的声明的类型的每个子对象。 如果声明集合不全来自相同类型的子对象,或者集合具有非静态成员并且包括来自不同子对象的成员,则存在歧义,程序是非法的。否则,该集合是查找的结果。

class A {
public:
  int f(int);

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

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

因此,您可以使用using声明A::fB::f来消除歧义。

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

};

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

第二段代码可以无误运行是因为void foo(float)在C的作用域内。实际上,d.foo(5);调用的是void foo(float)而不是int版本。


2
被调用的 void foo(float) 版本真的让我有些困惑.. 感谢您详尽的回答。 :) - Xeo
1
脑海中浮现的一个问题是……是否存在这样一种情况,即在基类函数具有不同签名的情况下想要隐藏它们?对于相同签名的函数,当然很有用,但是对于不同签名的函数,我就无法想象出一个好的例子了…… - Xeo
2
如果生成的声明集合包括来自不同子对象的成员,则存在歧义,程序将无法形成。这个规则的原因是什么?否则会发生什么的例子会很好。 - Olumide

9

Name lookup 是和 overload resolution 不同的阶段。

首先进行 Name lookup。这个过程用于决定名称适用的作用域。在这种情况下,我们必须决定 d.foo 是否表示 d.D::food.B1::foo 还是 d.B2::foo。名称查找规则不考虑函数参数或其他任何内容;它纯粹关注名称和作用域。

仅当该决定已经做出后,我们才会在找到名称的作用域中执行函数的不同重载形式的 overload resolution。

在你的示例中,如果存在这样一个函数,调用 d.foo() 会找到 D::foo()。但是不存在这个函数。因此,回溯到基类中,它尝试了寻找 B1::fooB2::foo,所以它是有二义性的。

出于同样的原因,在 D 成员函数内部调用未限定的 foo(5); 也会有二义性。


推荐解决方案的效果:

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

这将创建名称为D::foo的函数,并使其识别两个函数。结果是d.foo解析为d.D::foo, 然后可以在由D::foo标识的这两个函数上进行重载分辨率。
注意:在此示例中,D::foo(int)Base1::foo(int)是一个函数的两个标识符;但通常,在名称查找和重载分辨过程中,它们是否是两个单独的函数并不重要。

2

这对你是否有效?

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接