以下是纯粹学术上发明的类层次结构。
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是错误的。这是对的吗?
答案 0 :(得分:2)
我不认为其中任何一个都是不正确的。首先,对于using X::f2
,查找X
,这将明确地产生类类型X
。然后查找f2
中的X
,这也是明确的(在D
中没有查找!)。
第二种情况也会出于同样的原因。
但是如果您在f2
对象上调用 D
,则调用将是不明确的,因为在{的所有子对象中都会查找名称f2
类型为D
的{1}},X
有两个这样的子对象,D
是非静态成员函数。同样的理由适用于第二种情况。对于您是否直接使用f2
或f3
命名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
)。但标准所说的是分别在int
和5.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::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
由Y
和Z
继承X
。因此,它是Y
和Z
的第一类成员。 E
不需要了解X
,因为它是隐藏的实现细节。所以,你想要
struct D : Y, Z{
using Y::f2; // error: inaccessible
using Z::f3;
};
按照你的要求解释9.1 / 2:
将一个类名插入到 声明的范围 在类名之后立即 看到。还插入了类名 进入班级本身的范围; 这被称为 注射类名。
名称X
作为X
注入X::X
。然后将其继承到Y
和Z
。 Y
和Z
不会在自己的范围内隐含声明X
。
以下步骤定义类中名称查找的结果 范围,C。首先,每个声明 类中的名称和每个中的名称 它的基类子对象是 考虑。 ... 如果得到一组声明 不是全部来自子对象 相同的类型,或集合有一个 非静态成员,包括成员 来自不同的子对象,有一个 歧义和程序是 病态的。否则那个集就是了 查找结果。
请注意,我将多个单词子对象加粗。虽然在两个子对象中找到了名称X
,但它们都是相同的类型,即X
。