死亡钻石和作用域解析运算符 (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();
}
我想在Top
class中给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
的部分是明确的。这是从 b
到 Top
的隐式转换,这是不明确的。您必须通过执行类似以下操作来明确消除前进方向的歧义:
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-specifier 是 B::
并且在其中查找 A
。然后 B::A
是一个嵌套名称说明符,表示 A
并且 tell
在其中查找。
显然 MSVC 接受了这个例子。可能它有一个非标准的扩展,通过这样的说明符回溯来解决歧义。
实际上,我在 Visual Studio 2019 年尝试时,提供的代码运行良好。
解决钻石问题有两种方法;
- 使用范围解析运算符
- 继承基础 class 作为虚拟
通过b.Right::Top::print()
调用打印函数应该没有错误地执行。但是你的基础class(顶部)仍然有两个对象从你的底部class.
引用
您可以在 here
中找到更多详细信息
我有这个代码(钻石问题):
#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();
}
我想在Top
class中给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
fromRight
and not fromLeft
.
那是你的意图,但实际情况并非如此。 Right::Top::print()
显式命名要调用的成员函数,即&Top::print
。但它没有指定我们在 b
的哪个子对象上调用该成员函数。您的代码在概念上等同于:
auto print = &Bottom::Right::Top::print; // ok
(b.*print)(); // error
选择print
的部分是明确的。这是从 b
到 Top
的隐式转换,这是不明确的。您必须通过执行类似以下操作来明确消除前进方向的歧义:
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-specifier 是 B::
并且在其中查找 A
。然后 B::A
是一个嵌套名称说明符,表示 A
并且 tell
在其中查找。
显然 MSVC 接受了这个例子。可能它有一个非标准的扩展,通过这样的说明符回溯来解决歧义。
实际上,我在 Visual Studio 2019 年尝试时,提供的代码运行良好。 解决钻石问题有两种方法; - 使用范围解析运算符 - 继承基础 class 作为虚拟
通过b.Right::Top::print()
调用打印函数应该没有错误地执行。但是你的基础class(顶部)仍然有两个对象从你的底部class.
您可以在 here
中找到更多详细信息