什么是预期的行为?

时间:2010-08-23 09:40:32

标签: c++ declaration using ambiguity

以下是纯粹学术上发明的类层次结构。

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(){}

这里我曾尝试(故意)使用声明创建类型问题。 Gcc仍然编译这个罚款,VS2010也是如此。 Comeau仍然会出现关于模糊类型'麻烦'的错误(正如预期的那样)。通过对初始查询给出的解释,看起来GCC和VS2010是错误的。这是对的吗?

3 个答案:

答案 0 :(得分:2)

我不认为其中任何一个都是不正确的。首先,对于using X::f2,查找X,这将明确地产生类类型X。然后查找f2中的X,这也是明确的(在D中没有查找!)。

第二种情况也会出于同样的原因。

但是如果您在f2对象上调用 D,则调用将是不明确的,因为在{的所有子对象中都会查找名称f2类型为D的{​​1}},X有两个这样的子对象,D是非静态成员函数。同样的理由适用于第二种情况。对于您是否直接使用f2f3命名Z::X,这没有任何区别。这两个都指定了类X

要获得使用声明的歧义,您需要以不同的方式编写它。请注意,在C ++中,0x X无效。但它在C ++ 03中,只要整个名称引用基类成员。

相反,如果在C ++ 0x中允许这样做,那么整个using声明也是有效的,因为C ++ 0x不会将子对象考虑进行名称查找:using ThisClass::...;仅明确指代一个声明D::f2中的声明)。请参阅DR #39和最终论文N1626

X

C ++ 03标准在段落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; }; 10.2中描述了这一点。


对Edit3的响应

是的,GCC和VS2010都错了。 3.4.3.1是指注入的类名trouble找到的类型,以及找到::trouble的嵌套类。 Y::trouble之前的名称trouble使用非限定查找(由::查找,该查询委派给第一个项目符号中的3.4.1/7)忽略任何对象,函数和枚举器名称( 10.2 - 在这种情况下没有这样的名字,但是)。然后它违反了3.4.3/1的要求:

  

如果生成的声明集不是全部来自同一类型的子对象......程序就是格式错误。


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

  

在用作成员声明的using声明中,嵌套名称说明符应命名要定义的类的基类。

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

有关措辞不清晰的示例,请考虑以下表达式:

10.2

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

  

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

     

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

这是错误的用法,因为它根本没有消除歧义。它应该使用“can only”,编译器以这种方式解释它。这主要是历史原因,正如一些委员会成员最近在usenet上告诉我的那样。见The rules for the structure and drafting of International Standards,附件H.

答案 1 :(得分:0)

使用X :: f2;因下面代码的私有继承而无法工作

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

无法访问X到Y的成员。 所以X :: f2会发生冲突。

Z::X::f2应该有效。 或Z::f2应该有效。

答案 2 :(得分:0)

首先,澄清约翰内斯的回答。当您说using Z::X::f2;时,编译器不会“构建路径”到f2以跟踪应该如何访问它。由于Z::XX相同,因此声明与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令人作呕,因为每个类都会将自己的名称带入自己的范围。因此::这里不表示继承。

现在,解决问题。 f2YZ继承X。因此,它是YZ的第一类成员E不需要了解X,因为它是隐藏的实现细节。所以,你想要

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

按照你的要求解释9.1 / 2:

  

将一个类名插入到   声明的范围   在类名之后立即   看到。还插入了类名   进入班级本身的范围;   这被称为   注射类名。

名称X作为X注入X::X。然后将其继承到YZYZ不会在自己的范围内隐含声明X

10.2 / 2:

  

以下步骤定义类中名称查找的结果   范围,C。首先,每个声明   类中的名称和每个中的名称   它的基类子对象是   考虑。 ...   如果得到一组声明   不是全部来自子对象   相同的类型,或集合有一个   非静态成员,包括成员   来自不同的子对象,有一个   歧义和程序是   病态的。否则那个集就是了   查找结果。

请注意,我将多个单词子对象加粗。虽然在两个子对象中找到了名称X,但它们都是相同的类型,即X