C++ 成员名称查找和访问声明中的歧义

Ambiguity in Member Name LookUp and Access Declarations in C++

class A                      { public: int a;       };                 
class B : public virtual A   { public: using A::a;  };                 
class C : public virtual A   { public: using A::a;  };                 
class D : public C, public B {                      };                 

class W                      { public: int w;       };                  
class X : public virtual W   { public: using W::w;  };                  
class Y : public virtual W   {                      };                  
class Z : public Y, public X {                      };

int main(){

    D d;
    d.a = 0; // Error

    Z z;                                                               
    z.w = 0; // Correct

    return 0;
}        

第一组 class 声明(ABCD)和第二组(WXYZ) 的构建方式类似,只是 class C 有一个 using 声明 (using A::a) 而 class Y 没有。

尝试访问 d.a = 0 中的成员 a 时,我在 Clang (error: member 'a' found in multiple base classes of different types) 和 GCC (error: request for member 'a' is ambiguous) 中遇到错误。我检查了两个编译器的最新版本和旧版本,它们都同意。但是,在 z.w = 0 中访问 w 编译成功。

访问a时出现这种歧义的原因是什么?

据我所知,classes BC 中的两个访问声明都引用相同的基础 class 成员。顺便说一句,如果我删除它们,测试将成功编译,因为 a 已经可以公开访问( public 访问说明符)。

提前致谢。

注意:以上代码是对SolidSands' SuperTest 套件稍作修改的测试。

此处存在实施差异; ICC 接受您的代码,而 gcc、clang 和 MSVC 拒绝它。 ICC 正确,其他编译器不正确。

运行[class.member.lookup]算法D::a,我们发现:

  • D中没有a的声明,所以S(a,D)最初是空的,我们在其基[=]中合并a的查找集88=]es,计算如下:
    • S(a, B) = { { A::a }, { B } }
    • S(a, C) = { { A::a }, { C } }
  • 生成的查找集是 S(a, D) = { { A::a }, { B, C } }

请注意,在 S(a, B) 的 声明集 中,成员是 A::a,即使它在 B 中找到,并且同样对于 S(a, C):

In the declaration set, using-declarations are replaced by the set of designated members [...]

为了判断成员访问d.a是否有歧义,我们现在检查[expr.ref]/5:

5 - [The] program is ill-formed if the class of which E2 is directly a member is an ambiguous base of the naming class of E2 [...]

此处E2已确定为A::aA的直系成员。命名class是DA 不是 D 的模糊基,因为 AD 的所有中间基 class 子对象的虚拟基。所以 d.a 在名称查找和成员访问方面都是明确的,你的程序是正确的。


作为一个类似的例子,我们可以考虑根据 [class.member.lookup]/9 的注释用静态成员替换虚拟继承:

9 - [ Note: A static member, a nested type or an enumerator defined in a base class T can unambiguously be found even if an object has more than one base class subobject of type T. Two base class subobjects share the non-static member subobjects of their common virtual base classes. — end note ]

struct A { static int a; };                 
struct B : A { using A::a; };                 
struct C : A { using A::a; };                 
struct D : B, C { };                 

int main() {
    D d;
    d.a = 0; // OK
}

在这里我们又得到了 S(a, D) = { { A::a }, { B, C } }。实际上,即使 A::a 是非虚拟基的非静态成员,名称查找也会以相同的方式进行; name lookup 是明确的,但是 member access [expr.ref] 是模棱两可的案例.

为了进一步阐明名称查找和成员访问之间的区别,请考虑即使对于非虚拟多重继承基 class 的非静态数据成员,也可以明确地使用名称以派生class为命名的成员class:

struct A { int a; };
struct B : A { using A::a; };
struct C : A { using A::a; };
struct D : B, C { };
int B::*p = &D::i;      // OK, unambiguous

不幸的是,我尝试过的每个编译器都拒绝了这个,尽管这是(模使用声明)an example in the Standard