Diamond of death and Scope resolution operator (c++)
我有这个代码(钻石问题):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #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班的print()。
当我试图编译它时,我得到了错误:'Top' is an ambiguous base of 'Bottom'在这行:b.Right::Top::print();为什么模棱两可?我明确指出我要从Right而不是从Left得到Top。
我不想知道怎么做,是的,它可以通过引用、虚拟继承等来完成。我只想知道为什么b.Right::Top::print();不明确。
- "如果类成员访问运算符(包括隐式的"this->")用于访问非静态数据成员或非静态成员函数,则如果左操作数(在"."运算符的情况下被视为指针)无法隐式转换为指向右操作的命名类的指针,则引用的格式不正确。",11.2p6.注意,命名类是A,但是D*不能隐式转换为A*。
- 这里的语义是,您告诉它您想用B::A::tell调用什么函数。您可以帮助编译器使用D::tell,即名称查找。但是您没有指定它必须使用的子对象——它有两个选择:沿着A的道路,超过B或超过C,并且会给您带来错误。
- 在运行时操作对象的上下文中,有两个"主要"的歧义检查:一个在5.2.5p5中,另一个在11.2p6中咬你一口。如果删除除A以外的所有tell函数,5.2.5p5中的函数将拒绝d.tell(),因为命名类当时是D,但tell将是A的直接成员,A不明确。如果你说D.B::A::tell(),它是由5.2.5p5形成的,但是由11.2p6形成的。这些检查相互补充,对于类型系统的正确操作很重要。
- 下面是一个相关的问题,其中MSVC++也接受:stackoverflow.com/q/4130201/34509
Why is it ambiguous? I explicitly specified that I want Top from Right and not from Left.
号
这是你的意图,但事实并非如此。Right::Top::print()显式地命名要调用的成员函数,即&Top::print。但是它没有指定我们调用该成员函数的b的哪个子对象。您的代码在概念上等价于:
1 2
| auto print = &Bottom::Right::Top::print; // ok
(b.*print)(); // error |
选择print的部分是明确的。从b到Top的隐式转换是模糊的。你必须通过做如下的事情来明确地消除你要朝哪个方向走的歧义:
1
| static_cast<Right&>(b).Top::print(); |
号
- 那么为什么我用Right::print()而不是Right::Top::print()呢?两者都不使用指向成员的指针,但b.Right::print()将起作用。(当然,我已经在Right类中删除了print())。
- @因为从b到Right的转换是明确的。只有一个Right类型的子对象。
- 感谢您的回复。如果从b到Right的转换是明确的,为什么:auto print = &Bottom::Right::print;(b.*print)()给ambiguous base错误(右是空类)?
- @PCAF不含糊。与您发布的层次结构不同。
- ideone.com/ywuhwz网站
- @PCAF好吧,但这和你发布的层次结构不同…
- 在这两条评论中,我都写道,删除了print()
- @PCAF关于您的后续评论。与&Bottom::Right::print一起使用是不明确的,因为它的类型实际上是void(Top::*)()。类型系统不会记住您使用Bottom::Right而不是Bottom::Right::Top的信息。
作用域解析运算符是左相关的(尽管它不允许使用括号)。
因此,当您想在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:
type-name ::
nested-name-specifier identifier ::
号
因此,第一个嵌套的名称说明符是B::,在其中查找A。那么,B::A是一个嵌套的名称说明符,表示A,在其中查找tell。
显然,MSVC接受了这个例子。可能它有一个非标准的扩展,通过这种说明符进行回溯来解决歧义。
- 请引用相关标准,否则这是推测。
- @干杯,谢谢。-我贴完后,加了一个[语言律师]标签。我会看看我是否能想出一个好的解释,但请注意,从右到左进行查找是完全不可能的。
- @potatoswatter+1,并查看我对问题的评论以供参考。
- 我鼓励你把答案转移到这里:stackoverflow.com/q/36779466/34509,而不是重复的问题。
事实上,在我在Visual Studio 2019上尝试时,提供代码工作得很好。有两种方法可以解决钻石问题;-使用范围解析运算符-作为虚拟继承基类
执行b.Right::Top::print()调用print函数时应无误。但是仍然有两个从底层类引用的基类(top)对象。
你可以在这里找到更多的细节