【问题标题】:Why doesn't a using-declaration work to solve the diamond problem?为什么使用声明不能解决钻石问题?
【发布时间】:2015-07-14 04:48:28
【问题描述】:

请考虑以下代码:

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();

我从这个例子中得到了一个使用声明应该起作用的印象:

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

【问题讨论】:

  • 你需要 a::f 作为虚拟方法来覆盖它
  • 我没有覆盖任何东西。我躲起来了。
  • 可能:没有B1::f(名称解析为A::f),因此你有两个A::f(一个在B1,一个在B2)
  • @0x499602D2:删除C::f 定义。
  • 再说一遍,你是一个在巨大大多数情况下是净收益的规则的不幸受害者。

标签: c++ inheritance subclass using-directives diamond-problem


【解决方案1】:

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

[ 注意:因为 using-declaration 指定了一个基类成员 (而不是成员子对象或基类的成员函数 子对象),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
}

尾注 ]

【讨论】:

  • 太好了,谢谢。我愿意接受这个答案,但是您能否用您的话解释一下“指定基类成员(而不是成员子对象或基类子对象的成员函数)”的含义。嗯……什么?
  • @T.C.规范性参考似乎是 [expr.ref]/5。
  • @gd1 using-declaration 指定成员 (A::x()) 而不是子对象(CB 包含具有 Ax()) .您需要指定子对象来解决歧义。
  • @Brian 当然,这就是最终导致调用格式错误的原因,但要完整,您需要经历的远不止这些。由于该问题没有要求进行完整的语言律师分析,因此我认为该说明已经足够好了。
  • @gd1 我本来打算去的,但后来 Brian 发布了他的答案,我没有太多要补充的。
【解决方案2】:

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

它不起作用,因为 using-declaration 只会影响名称查找。

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

即使C 有两个A 子对象,也只有一个函数A::f,并且它采用this 类型的隐式this 参数A*。为了在C 对象上调用它,C* 必须隐式转换为A*。这总是模棱两可的,不受任何using-declarations的影响。

(如果将数据成员放在A 中,这更有意义。那么C 将有两个这样的数据成员。当f 被调用时,如果它访问数据成员,它会访问继承自B1A 子对象,还是继承自B2A 子对象?)

【讨论】:

  • 所以,using-declare 不够聪明,否则应该访问从 B1 继承的 A 中的那些。
【解决方案3】:

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

这里是关于处理使用声明的内容:

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

所以当你尝试在struct C 中声明时

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

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

【讨论】:

  • 这与布赖恩的回答有何关系?
  • 布赖恩非常明确的答案同时发布。如果我先阅读他的答案,我不会发布我的答案 ;-) 但我不会删除我的答案,只是为可能感兴趣的语言律师留下标准参考。
  • 我不认为你的回答没用,我只是想了解它与布赖恩的回答有何关系。我不明白 10.2/3 如何解释我所看到的,而 T.C. 报告的说明和布赖恩对它的解释对我来说很有意义——当一起使用时;)
  • 我已经完成了第一句话的引用。我的解释只集中在声明集(以及 tehre 是模棱两可的事实)。 Brian 还解决了子对象集以及您的示例的特殊情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-19
  • 2022-01-07
  • 2021-04-04
  • 1970-01-01
相关资源
最近更新 更多