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



请考虑以下代码:

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

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

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

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

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

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

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

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

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. 的回答之外,我还想补充一点,派生类中的名称查找在标准中在 10.2 节中进行了非常详细的解释。

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

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

因此,当您尝试在struct C中声明时

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

根据查找规则,编译器仍然会找到可能的候选项:B1::fB2::f,因此它仍然不明确。

最新更新