死亡钻石和作用域解析运算符 (c++)

Diamond of death and Scope resolution operator (c++)

我有这个代码(钻石问题):

#include <iostream>
using namespace std;

struct Top
{
    void print() { cout << "Top::print()" << endl; }
};

struct Right : Top 
{
    void print() { cout << "Right::print()" << endl; }
};

struct Left : Top 
{
    void print() { cout << "Left::print()" << endl; }
};

struct Bottom: Right, Left{};

int main()
{
    Bottom b;
    b.Right::Top::print();
}

我想在Topclass中给print()打电话。

当我尝试编译它时出现错误:'Top' is an ambiguous base of 'Bottom' 这一行:b.Right::Top::print(); 为什么会模棱两可?我明确指定我想要 Right 而不是 Left.

Top

我不想知道怎么做,是的,可以通过引用、虚拟继承等来完成。我只想知道为什么 b.Right::Top::print(); 不明确。

Why is it ambiguous? I explicitly specified that I want Top from Right and not from Left.

那是你的意图,但实际情况并非如此。 Right::Top::print() 显式命名要调用的成员函数,即&Top::print。但它没有指定我们在 b 的哪个子对象上调用该成员函数。您的代码在概念上等同于:

auto print = &Bottom::Right::Top::print;  // ok
(b.*print)();                             // error

选择print的部分是明确的。这是从 bTop 的隐式转换,这是不明确的。您必须通过执行类似以下操作来明确消除前进方向的歧义:

static_cast<Right&>(b).Top::print();

范围解析运算符是左关联的(尽管它不允许括号)。

因此,虽然您想在 B 中引用 A::tell,但 id 表达式在 B::A 中引用 tell,这就是 A ,这是模棱两可的。

解决方法是首先转换为明确的基数 B,然后再次转换为 A

语言律师:

[basic.lookup.qual]/1 说,

The name of a class or namespace member or enumerator can be referred to after the :: scope resolution operator applied to a nested-name-specifier that denotes its class, namespace, or enumeration.

nested-name-specifier 的相关语法是,

nested-name-specifier:

    type-name ::

    nested-name-specifier identifier ::

因此,第一个 nested-name-specifierB:: 并且在其中查找 A。然后 B::A 是一个嵌套名称说明符,表示 A 并且 tell 在其中查找。

显然 MSVC 接受了这个例子。可能它有一个非标准的扩展,通过这样的说明符回溯来解决歧义。

实际上,我在 Visual Studio 2019 年尝试时,提供的代码运行良好。 解决钻石问题有两种方法; - 使用范围解析运算符 - 继承基础 class 作为虚拟

通过b.Right::Top::print() 调用打印函数应该没有错误地执行。但是你的基础class(顶部)仍然有两个对象从你的底部class.

引用

您可以在 here

中找到更多详细信息