为什么使用声明不能解决钻石问题?

Why doesn't a using-declaration work to solve the diamond problem?

提问人:gd1 提问时间:5/5/2015 最后编辑:Cœurgd1 更新时间:11/9/2018 访问量:3494

问:

请考虑以下代码:

struct A
{
    void f()
    {
    }
};

struct B1 : A
{
};

struct B2 : A
{
};

struct C : B1, B2
{
    void f() // works
    {
        B1::f();
    }
    //using B1::f; // does not work
    //using B1::A::f; // does not work as well
};

int main()
{
    C c;
    c.f();

    return 0;
}

我恳请您不要复制粘贴关于如何解决钻石问题的标准回复(“使用虚拟继承”)。我在这里要问的是,为什么在这种情况下,使用声明不起作用。确切的编译器错误是:

In function 'int main()':
prog.cpp:31:6: error: 'A' is an ambiguous base of 'C'
  c.f();

从这个例子中,我得到的印象是 using 声明应该起作用:

struct A
{
    void f()
    {
    }
};

struct B
{
    void f()
    {
    }
};

struct C : A, B
{
    using A::f;
};

int main()
{
    C c;
    c.f(); // will call A::f

    return 0;
}
C++ 继承 子类 using-directives diamond-problem

评论

0赞 progsource 5/5/2015
您需要 a::f 作为虚拟方法来覆盖它
4赞 gd1 5/5/2015
我没有凌驾于任何之上。我躲起来了。
1赞 5/5/2015
可能:没有 B1::f(名称解析为 A::f),因此您有两个 A::f(一个在 B1 中,一个在 B2 中)
2赞 Jarod42 5/5/2015
@0x499602D2:删除定义。C::f
6赞 Lightness Races in Orbit 5/5/2015
关于标题,您是一条规则的不幸受害者,该规则在绝大多数情况下都是净收益。

答:

29赞 T.C. 5/5/2015 #1

[namespace.udecl]/p17 中有一个注释直接解决了这种情况:

[ 注意:因为 using 声明指定基类成员 (而不是基类的成员子对象或成员函数 subobject),using-declaration 不能用于解析继承的 成员歧义。例如

struct A { int x(); };
struct B : A { };
struct C : A {
    using A::x;
    int x(int);
};
struct D : B, C {
    using C::x;
    int x(double);
};
int f(D* d) {    
    return d->x(); // ambiguous: B::x or C::x
}

——尾注 ]

评论

6赞 gd1 5/5/2015
太好了,谢谢。我愿意接受这个答案,但你能用你的话解释一下“指定基类成员(而不是成员子对象或基类子对象的成员函数)”是什么意思吗?嗯......什么?
0赞 Brian Bi 5/5/2015
@T.C.规范性参考似乎是 [expr.ref]/5。
0赞 Barry 5/5/2015
@gd1 using 声明指定一个成员 () 而不是一个子对象(或 或 包含具有 的 )。您需要指定子对象来解决歧义。A::x()CBAx()
1赞 T.C. 5/5/2015
@Brian 当然,这就是最终导致通话格式错误的原因,但要完成,您需要经历更多的事情。由于这个问题没有要求进行完整的语言律师分析,我认为这个笔记已经足够好了。
0赞 T.C. 5/5/2015
@gd1我本来打算去的,但后来布莱恩发布了他的答案,我没有什么要补充的。
57赞 Brian Bi 5/5/2015 #2

其他人可以找到标准报价,但我将从概念上解释。

它不起作用,因为 using 声明只影响名称查找。

您的 using 声明会导致名称查找成功,否则它会失败,也就是说,它告诉编译器在哪里可以找到函数 f但它并没有告诉它哪个子对象 f 作用于哪个子对象,也就是说,哪个子对象在被调用时将作为隐式参数传递。thisf

即使有两个子对象,也只有一个函数,并且它接受一个 类型的隐式参数。为了在对象上调用它,必须隐式转换为 。这始终是模棱两可的,并且不受任何使用声明的影响。A::fACthisA*CC*A*

(如果将数据成员放在 中,则更有意义。然后每个这样的数据成员将有两个。调用时,如果它访问数据成员,它是访问继承自的子对象中的子对象,还是继承自的子对象中的子对象?ACfAB1AB2

评论

0赞 user1633272 4/1/2017
所以,using-declare 不够聪明,否则应该访问从 B1 继承的 A 中的那些。
5赞 Christophe 5/5/2015 #3

除了 T.C. 的回答之外,我还想补充一点,派生类中的名称查找在第 10.2 节中对标准进行了非常详细的解释。

以下是关于处理使用声明的内容:

10.2/3:查找集 (...) 由两个组件集组成:声明集,一组名为 f 的成员;和子对象集,一组子对象,其中声明这些成员(可能包括 using-declarations)被发现。在声明集中,using 声明它们指定的成员替换,并键入声明(包括 injected-class-names) 替换为它们指定的类型。

因此,当您尝试声明struct C

using B1::f; // you hope to make clear that B1::f is to be used

根据查找规则,编译器仍然会找到可能的候选者:因此它仍然是模棱两可的。B1::fB2::f

评论

0赞 gd1 5/5/2015
这与布莱恩的回答有什么关系?
0赞 Christophe 5/5/2015
布莱恩非常明确的答案在同一时间发布。如果我先读他的答案,我就不会发布我的答案;-)但我没有删除我的,只是为了给可能感兴趣的语言律师留下标准参考。
0赞 gd1 5/5/2015
我不认为你的答案是无用的,我只是想了解它与布莱恩的关系。我不明白 10.2/3 如何解释我所看到的,而 T.C. 报告的注释和 Brian 对它的解释对我来说是有意义的——当一起使用时;)
0赞 Christophe 5/5/2015
我已经用第一句话完成了引用。我的解释只集中在声明集上(以及 tehre 是模棱两可的事实)。Brian 还介绍了子对象集和示例的特殊情况。