【问题标题】:Ambiguity in Member Name LookUp and Access Declarations in C++C++ 中成员名称查找和访问声明的歧义
【发布时间】:2017-06-19 09:08:49
【问题描述】:
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 声明 (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时出现这种歧义的原因是什么?

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

提前致谢。

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

【问题讨论】:

  • d.a 可以在d.B::ad.C::a 之间进行选择(即使实际上是一样的)。
  • 为什么不使用d.A::a = 0;
  • 我在cppreference 找不到任何适用于此处的using 文档(仅别名和与命名空间相关的用途)。
  • C++11 中成员访问规则发生了变化。据我所知,您的示例对 C++11 完全有效。 C++03 规则 我相信 使您的第一个代码格式错误,因为它不考虑使用透明的声明(查找不通过它们查找 C++03):它将找到“w " 在 B 和 C 中,没有一个比另一个更占优势。对于第二个测试用例,它在 W 和 X 中找到“w”,但 X 比 W 更占优势,因此 X 获胜。在 C++11 中,在两个测试用例中,它分别在 W 和 A 中找到“w”,然后就没有歧义了。
  • @jos 是的,所以我不知道,当时似乎在 C++03 中也有效。另一方面,它并没有说这两个 using 声明是合并的。它只是说两者都被认为来自同一个子对象(出于这两个检查的目的)。所以名称查找仍然会找到两个声明。也许这就是编译器抱怨的原因?

标签: c++ c++11 c++14 member c++03


【解决方案1】:

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

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

  • D 中没有a 的声明,因此 S(a, D) 最初为空,我们在其基类中合并a 的查找集,计算如下:
    • 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) 也是如此:

在声明集中,using-declarations被指定成员的集合替换[...]

要判断成员访问d.a是否有歧义,我们现在检查[expr.ref]/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(a, D) = { { A::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

【讨论】:

  • 您答案的最后一部分是声称实现 C++11 的现有实现的一个长期存在的问题,请参阅 groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/…thecodingforums.com/threads/…
  • 还有groups.google.com/forum/#!msg/comp.lang.c++.moderated/…,因为关于使用 10.2 规则的重载解决方案的措辞不明确。尽管我将 DR 直接发布给负责人,但所有这些都没有在规范和实现中得到解决(据我所知)。
  • @ecatmur 非常感谢您的解释,我同意代码对 C++11 和 C++14 有效。你认为代码对 C++03 也有效吗?您引用的确切算法尚未定义,而 [class.member.lookup] 中只有一个段落 (10.2/2) 来定义此行为(这里引用了 C++03 段落:stackoverflow.com/a/7211847/6892577 )
  • @JoséLuis 我相信它也适用于 C++03。名称隐藏不会出现,所以重要的是d.B::a 被“认为是”来自d.B::A,同样d.C::a 来自d.C::A,由于虚拟继承,它们是相同的子对象;所以d.a 明确指定唯一基类子对象Aa 非静态数据成员。
  • @ecatmur 太好了,我想现在我们都同意它是有效的。非常感谢您的帮助。
猜你喜欢
  • 1970-01-01
  • 2015-07-23
  • 1970-01-01
  • 2015-10-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-17
  • 1970-01-01
相关资源
最近更新 更多