为什么允许使用冗余类名限定符?

时间:2012-07-10 23:17:12

标签: c++ language-lawyer

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

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基础子对象。我相信它实际上是作为一个名为i的新变量的变量声明解析,类型为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和gcc创建类型为b的变量A。 VS11错误说明b是一个未知的标识符。 VS11似乎使用A::A(i)作为参数的构造函数A来解析A::A(int)作为临时i的创建。当消除冗余限定符时,VS将源解析为变量声明,如clang和gcc do,并产生类似的关于隐藏变量i的错误。

解析的这种差异解释了为什么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是错误的,并且在如何解析冗余限定符时是否正确且是gcc?
  3. 我知道VS在标准合规性方面仍然落后一点,但我确实发现现代的,积极开发的编译器可能会如此分歧,在这种情况下将冗余限定符解析为构造函数的名称有点令人惊讶(即使构造函数没有名称)与简单地将冗余限定符解析为类型,导致VS构造一个临时的,其他人声明一个变量。在clang和gcc解析B b(A::A(i));作为最令人烦恼的解析时,可能会更糟糕,但VS认为它是使用初始化程序声明类型为b的变量B。这种严重的差异还有很多吗?
  4. 显然,在便携式代码中应避免使用冗余限定符。有没有一种好方法可以防止使用这种结构?

2 个答案:

答案 0 :(得分:7)

虽然这种现象很可能归因于类名注入,正如ephemient的回答所指出的,对于这个具体的例子,它已经被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;

等等,具有任意数量的A s。但不是了。令人惊讶的是,ideone使用的GCC版本(4.3.4?)仍然存在这个问题

http://ideone.com/OkR0F

您可以尝试使用您的VC11版本,看看是否允许这样做。

答案 1 :(得分:3)

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

  

² class-name 被插入到在看到 class-name 之后立即声明它的范围内。 class-name 也会插入到类本身的范围内,这称为 inject-class-name

写作时

A::A(i);

它与声明

相同
A i;

因为额外的括号是多余的(您可以添加任意数量的括号),A::A指的是A


来自§14.6.1,

  

¹与普通(非模板)类一样,类模板具有注入类名(第9条)。 inject-class-name可以用作模板名称类型名称。当它与 template-argument-list 一起使用时,作为模板 template-parameter template-argument ,或作为最终标识符在友元类模板声明的 elaborated-type-specifier 中,它引用了类模板本身。否则,它等同于 template-name ,后跟<>中包含的类模板的 template-parameters

注入的类名似乎是一种方便,允许A<...>在类中简称为A