2017-06-19 81 views
1
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; 
}   

类声明(ABCD)和第二(WXYZ)的第一组被类似地建立不同的是class C具有使用声明(using A::a)和class Y没有。歧义在C++成员名的查找和访问声明

当试图访问ad.a = 0时,在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时出现这种模糊不清的原因是什么?

据我所知,类BC中的访问声明都指向相同的基类成员。顺便说一下,如果我删除它们,则测试编译成功,因为a已经公开访问(public访问说明符)。

在此先感谢。

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

+2

'd.a'有d.B ::了'和'特区::了'(即使在现实中,也有相同)之间'选择。 – Jarod42

+0

为什么不使用'd.A :: a = 0;'? –

+1

我不能在[cppreference]找到(http://en.cppreference.com/w/cpp/)此处适用using'的'任何文件(仅别名,命名空间有关的用途)。 – Walter

回答

1

这里存在实现差异; ICC接受你的代码,而gcc,clang和MSVC拒绝它。 ICC是正确的,其他编译器不正确。

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

  • 有一个在D没有a声明,所以S(A,d)最初是空的,我们在查找套a合并它的基类,计算如下:
    • S(A,B)= {{A ::一个},{B}}
    • S(A,C​​)= {{A ::一个}, {C}}
  • 所得查找集是S(一,d)= {{A ::一个},{B,C}}

注意,在声明集合S的(A,B ),所述构件是A::a即使它在B发现,同样地,对于S(A,C​​):

在声明集合,使用-声明由组指定的成员[取代。 ..]

要确定成员访问d.a是否不明确,我们现在检查[expr。定]/5:

5 - [该]是形成不良的程序,如果其中的E2是直接的成员的类是命名类的E2 [的暧昧碱...]

这里E2已被确定为A::a,的A直接成员。命名类是DA不是D暧昧的基础上,因为AD所有中间基类的子对象的虚拟基础。因此d.a在名称查询和成员访问中都是明确的,并且您的程序是正确的。


作为一个类似实例,我们可以考虑以每音符的静态成员替换虚拟继承到[class.member.lookup]/9:

9 - [注:甲静态成员,嵌套类型或在基类定义T枚举可以明确发现即使一个对象具有T型的多个基类子对象。两个基类子对象共享其公共虚拟基类的非静态成员子对象。 - 注完]

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(一,d)= {{A ::一个},{B,C}}。事实上,即使A::a是非虚拟基地的非静态成员,名称查找也会以相同的方式进行, 名称查询是明确的,但成员访问[expr.ref]在这种情况下是不明确的。

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

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

+0

您的答案的最后一部分对于声称实现C++ 11的现有实现来说是一个长期存在的问题,请参阅https://groups.google.com/a/isocpp.org/forum/#!topic/std-讨论/ _GKE1I7YkkY同样https://www.thecodingforums.com/threads/class-member-name-lookup-issues.742289/#post-4178715 –

+0

也https://groups.google.com/forum/#!msg/ comp.lang.C++。moderated/SuZAsTsL0zQ/PpjnP0IMfakJ在10.2中规则的重载分辨率措辞中的不明确性。所有这些在我的知识中都没有得到解决,即使我直接把DR发布给负责人。 –

+0

@ecatmur非常感谢您的解释,我同意代码适用于C++ 11和C++ 14。你认为代码对C++ 03有效吗?您引用的确切算法尚未定义,并且在[class.member.lookup]中只有一个段落(10.2/2)来定义此行为(这里有C++ 03段落的引用:https:/ /stackoverflow.com/a/7211847/6892577) –