为什么允许使用多余的类名限定符

Why are redundant class name qualifiers allowed?

本文关键字:许使用 多余 为什么      更新时间:2023-10-16

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

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测试版编译和运行的,没有错误或警告/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) {
                     ^

这种类型可以用多余的类限定来指代,这似乎很奇怪。

此外,VS11和clang/gcc对A::A(i)的解析似乎有所不同。如果我执行A::A(b) clang和gcc,则使用默认构造函数创建类型为A的变量b。VS11错误地指出b是未知标识符。VS11似乎使用以i为参数的构造函数A::A(int)A::A(i)解析为临时A的创建。当多余的限定符被消除时,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是错的吗?clang和gcc在如何解析冗余限定符方面是对的吗
  3. 我知道VS在标准遵从性方面仍然有点落后,但我确实觉得有点奇怪的是,现代的、主动开发的编译器可能会如此不同,在这种情况下,将冗余限定符解析为构造函数的名称(即使构造函数没有名称),而不是简单地将冗余限定词解析为类型,导致VS在其他人声明变量的地方构造临时变量。更糟糕的是,clang和gcc将B b(A::A(i));解析为最麻烦的解析,但VS认为这是用初始化器声明B类型的变量b。还有这么严重的差异吗
  4. 显然,在可移植代码中应该避免使用多余的限定符。有没有一个好的方法来防止这种构造被使用

虽然这种现象可能归因于类名注入,正如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;

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

http://ideone.com/OkR0F

你可以在你的VC11版本中尝试一下,看看它是否允许这样做。

根据ISO/IEC 14882:2011最终草案§9,

²在看到类名之后,将类名称插入到其声明的作用域中。类名也被插入到类本身的作用域中,这被称为注入的类名

当你写

A::A(i);

它与申报相同

A i;

因为额外的括号是多余的(你可以随意添加),而A::A指的是A


根据§14.6.1,

与普通(非模板)类一样,类模板有一个注入的类名(第9条)。注入的类名可以用作模板名类型名。当它与模板参数列表一起使用时,作为模板的模板参数,或者作为友元类模板声明的精化类型说明符中的最终标识符时,它指的是类模板本身。否则,它等效于<>中包含的类模板的模板名称,后跟的模板参数

注入的类名似乎是为了方便A<...>在类中被简单地称为A