7

下面是一个纯学术发明的类层次结构。

struct X{
        void f1();
        void f2();
        void f3();
};

struct Y : private X{
        void f4();
};

struct Z : X{
};

struct D : Y, Z{
        using X::f2;
        using Z::X::f3;
};

int main(){}

我希望使用 X::f2 的声明是模棱两可的,因为“X”是“D”的模棱两可的基础(X 的可见性与可访问性)。但是 g++ (ideone.com) 编译得很好。

我检查了 Online Comeau,它在按预期使用 X::f2 声明时出错。但是,它也为使用 Z::X::f3 的声明提供了歧义。

那么预期的行为是什么?

编辑1:

请参考标准的适当部分。

编辑2:

我检查了 VS 2010,它只对使用声明 X::f2 有异议。然而,这与“X”的歧义无关(如 gcc 和 Comeau 的情况)。它是关于“错误 C2876:'X':并非所有重载都可以访问”。

编辑3:

struct X{
    void f(){}
};

struct Y : X{
    struct trouble{
        void f(){}
    };

};

struct trouble : X{
};

struct letscheck : Y, trouble{
    using trouble::f;
};

int main(){}

在这里,我尝试(有目的地)在 using 声明中创建类型问题。Gcc 仍然可以很好地编译,VS2010 也是如此。Comeau 仍然给出关于模棱两可类型“麻烦”的错误(如预期的那样)。根据对初始查询的解释,看来 GCC 和 VS2010 是错误的。那是对的吗?

4

3 回答 3

2

我不认为这些都是不正确的。首先,查找 for using X::f2X这将明确产生类 type X。然后f2inX被查找,这也是明确的(它没有被查找D!)。

出于同样的原因,第二种情况也会起作用。

但是如果你调用 f2一个D对象,调用将是模棱两可的,因为这个名字是在所有类型f2的子对象中查找的,并且有两个这样的子对象,并且是一个非静态成员函数。第二种情况也是同样的道理。无论您是使用命名还是直接命名,这都没有区别。这两个都指定类。DXDf2f3Z::XXX

要使 using 声明产生歧义,您需要以不同的方式编写它。请注意,在 C++0xusing ThisClass::...;中无效。但它在 C++03 中,只要整个名称指的是基类成员。

相反,如果在 C++0x 中允许这样做,那么整个 using 声明也是有效的,因为 C++0x 不考虑名称查找的子对象:D::f2明确地只引用一个声明(在 中的那个X)。请参阅DR #39和最终论文N1626

struct D : Y, Z{
    // ambiguous: f2 is declared in X, and X is a an ambiguous base class
    using D::f2;
 
    // still fine (if not referred to by calls/etc) :)
    using Z::X::f3;
};

struct E : D {
  // ambiguous in C++03
  // fine in C++0x (if not referred to by an object-context (such as a call)).
  using D::f2;
};

C++03 标准在段落10.23.4.3.1.


对 Edit3 的回应

是的,GCC 和 VS2010 是错误的。trouble指的是通过注入的类名找到的类型,::trouble以及找到的嵌套类Y::troubletrouble前面的名称::是使用非限定查找(by 3.4.1/7,它10.2在第一个项目符号中代表)忽略任何对象、函数和枚举器名称(3.4.3/1但在这种情况下没有这样的名称)。然后它违反了10.2的要求:

如果声明的结果集并非全部来自同一类型的子对象......程序是格式错误的。


VS2010 和 GCC 可能会以不同于 Comeau 的方式解释 C++0x 措辞并追溯实现该措辞:

在用作成员声明的 using-declaration 中,nested-name-specifier 应命名正在定义的类的基类。

这意味着考虑了非基类但如果命名非基类是错误的。如果标准打算忽略非基类名称,它会说can only here,或者明确地拼写出来(两种做法都已完成)。然而,该标准并不是因为使用了应该可以。并且 GCC 实现了 C++0x 措辞,因为它拒绝其他完全好的 C++03 代码,只是因为 using 声明包含它的类名。

对于不明确的措辞的示例,请考虑以下表达式:

a.~A();

这在语法上是不明确的,因为如果是类对象,它可以是成员函数调用,但如果具有标量类型(例如) a,它可以是伪析构函数调用(这是一个无操作)。但是标准所说的是伪析构函数调用和类成员访问的语法分别在和aint5.2.45.2.5

点运算符的左侧应为标量类型。

对于第一个选项(点),第一个表达式(对象表达式)的类型应为“类对象”(完整类型)。

那是错误的用法,因为它根本无法消除歧义。它应该使用“can only”,编译器以这种方式解释它。正如一些委员会成员最近在 usenet 上告诉我的那样,这主要有历史原因。参见国际标准的结构和起草规则,附件 H。

于 2010-08-23T10:14:26.010 回答
0

使用 X::f2; 由于以下代码的私有继承,不应该工作

struct Y : private X{
    void f4();
};

无法通过 Y 访问 X 的成员。因此 X::f2 会发生冲突。

Z::X::f2应该管用。或者Z::f2应该工作。

于 2010-08-23T09:53:45.933 回答
0

首先,澄清约翰内斯的回答。当您说using Z::X::f2;时,编译器不会“构建路径”来f2跟踪应该如何访问它。既然Z::X是一回事X,声明就跟说的一模一样using X::f2;。将其与此示例进行对比:

struct A { void f() {} void g() {} };
struct B { void f() {} void g() {} };
struct C { typedef A X; };
struct D { typedef B X; };
struct E : A, B {
    using C::X::f; // C::X == A
    using D::X::g; // D::X == B
};

该语法的Z::X工作原理不是因为继承或成员资格,而是因为标识符X可以从范围访问Z。你甚至可以写得Z::Z::Z::Z::X::X::X::X令人作呕,因为每个类都将自己的名字带入自己的范围。因此::这里不表示继承。

现在,来解决问题。f2由 继承YZ从继承X。因此,它是和的一等成员。不需要知道,因为它是一个隐藏的实现细节。所以你要YZEX

struct D : Y, Z{
    using Y::f2; // error: inaccessible
    using Z::f3;
};

按照您的要求用 9.1/2 进行解释:

类名被插入到在看到类名之后立即声明它的作用域中。类名也被插入到类本身的范围内;这被称为注入类名。

名称X被注入Xas X::X。然后它被继承到YandZ中。Y并且Z不要X在自己的范围内隐式声明。

10.2/2:

以下步骤定义在类范围 C 中名称查找的结果。首先,考虑类中及其每个基类子对象中名称的每个声明。…如果声明的结果集并非全部来自相同类型的子对象,或者该集具有非静态成员并包含来自不同子对象的成员,则存在歧义并且程序格式错误。否则,该集合是查找的结果。

请注意,我将复数单词 sub-objects 加粗。虽然名称X出现在两个子对象中,但它们都是同一类型,即X.

于 2010-08-23T10:41:02.840 回答