10

我遇到了一些这样的代码:

struct A {
    A() {}
    A(int) {}
};

struct B : A {
    void init(int i);
};

void B::init(int i) {
    A::A(i); // what is this?
}

int main() {
    B b;
    b.init(2);
}

这使用 VC11 beta 编译和运行,没有 /W4 的错误或警告。

明显的意图是调用 B::init 来重新初始化 B 的 A 基子对象。我相信它实际上解析为一个名为itype的新变量的变量声明A。使用 clang 编译会产生诊断:

ConsoleApplication1.cpp:11:14: warning: declaration shadows a local variable
        A::A(i);
             ^
ConsoleApplication1.cpp:10:22: note: previous declaration is here
    void B::init(int i) {
                     ^
ConsoleApplication1.cpp:11:14: error: redefinition of 'i' with a different type
        A::A(i);
             ^
ConsoleApplication1.cpp:10:22: note: previous definition is here
    void B::init(int i) {
                     ^

可以用多余的类限定来引用该类型似乎很奇怪。

此外,A::A(i)VS11 和 clang/gcc 的解析方式似乎有所不同。如果我使用默认构造函数A::A(b)创建 clang 和 gccb类型的变量。AVS11 错误说b是未知标识符。VS11 似乎解析为使用构造函数作为参数A::A(i)创建临时。当冗余限定符被消除时,VS 像 clang 和 gcc 一样将源解析为变量声明,并产生类似的关于隐藏变量的错误。AA::A(int)ii

这种解析上的差异解释了为什么 VS11 会阻塞多个额外的限定符;A::A::A::A(i),以及为什么,鉴于 clang 和 gcc 可以接受一个额外的限定符,任何多于一个的数字与一个额外的结果相同。

这是另一个在不同上下文中使用冗余限定符的示例。所有编译器似乎都将其解析为临时结构:

class Foo {};

void bar(Foo const &) {}

int main() {
    bar(Foo::Foo());
}
  1. 为什么完全允许冗余限定符?
  2. 在某些情况下可以引用构造函数,例如继承构造函数的语法 ( class D : B { using B::B; };),但 VS 似乎允许在任何地方使用它。VS 错了吗,clang 和 gcc 在如何解析冗余限定符方面是否正确?
  3. 我知道 VS 在标准合规性方面仍然有点落后,但我确实发现现代、积极开发的编译器可能如此不同,在这种情况下将冗余限定符解析为构造函数的名称(即使构造函数没有名称)与将冗余限定符简单地解析为类型,导致 VS 在其他人声明变量的地方构造一个临时的。B b(A::A(i));在被 clang 和 gcc 解析为最令人头疼的解析时,情况可能会变得更糟,但 VS 将其视为使用初始化程序声明b类型变量。B这么严重的分歧还有很多吗?
  4. 显然,在可移植代码中应该避免冗余限定符。有没有防止使用这种构造的好方法?
4

2 回答 2

7

虽然这种现象可能归因于类名注入,正如 ehemient 的回答中所指出的那样,但对于这个特定的例子,它在很久以前就被 C++ 语言禁止了。

http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#147

该组合A::A需要引用类构造函数,而不是类注入名称。A::A(i)应该由兼容的编译器解释为涉及构造函数名称的非法(因此无意义)表达式。例如,Comeau 编译器会因为这个原因拒绝编译您的代码。

显然 VC11 继续将其A::A视为对注入类名的引用。有趣的是,我在 VS2005 中没有观察到这个问题。

早在A::A被解释为引用注入名称的那一天,人们可以将一个A对象声明为

A::A::A::A::A::A a;

依此类推,具有任意数量的As。但现在不是了。令人惊讶的是,ideone 使用的 GCC 版本(4.3.4?)仍然存在这个问题

http://ideone.com/OkR0F

你可以用你的 VC11 版本试试这个,看看它是否允许。

于 2012-07-11T00:41:40.617 回答
3

来自 ISO/IEC 14882:2011 最终草案§9,

²类名在看到类名后立即插入到声明它的范围内。类也被插入到类本身的范围内,这被称为注入类名

当你写

A::A(i);

和声明一样

A i;

因为多余的括号是多余的(您可以添加任意数量的)并A::A引用A.


从§14.6.1,

¹ 与普通(非模板)类一样,类模板具有注入类名称(第 9 条)。注入的类名可以用作模板名类型名。当它与template-argument-list一起使用时,作为模板模板参数的模板参数,或作为朋友类模板声明的详细类型说明符中的最终标识符,它指的是类模板本身. 否则,它等价于template-name后跟包含在 中的类模板的模板参数<>

注入的类名似乎很方便,可以在类A<...>中简单地引用A

于 2012-07-10T23:59:28.760 回答