奇怪的 C++ 命名空间解析怪癖以及 g++ 与 clang++

tor*_*rek 5 c++ language-lawyer c++11

这要从一次观察开始。我更改了一些看起来有点像这样的代码(编辑:我在这里取出了指定的初始值设定项,这些初始值设定项也不在原始代码中):

\n\n
struct S {\n    enum E { E1, E2 } member;\n}\n\n// file1.cc\nS v1 = { S::E1 };\n\n// file2.cc\nS v2 = { S::S::E2 };\n
Run Code Online (Sandbox Code Playgroud)\n\n

请注意,file2.cc过度限定E2. 但这在 g++ 和 clang++ 中都有效。(编辑 2:这个特定 VM 上的 g++ 是 g++-5.4.1,但原始代码已经经历了早期和晚期的 g++ 版本,以及多个 clang 版本。)事实上,我们可以编写:

\n\n
S v3 = { S::S::S::S::S::S::S::E1 };\n
Run Code Online (Sandbox Code Playgroud)\n\n

(无论S::我们喜欢多少),无论我们喜欢在哪里。我改变了一些东西,使它S不再是一个普通的struct,而是一个模板化的,之后就停止工作了。没什么大不了的,但这让我很好奇,所以我尝试了一下。

\n\n

如果我们将其更改为非 POD 类型:

\n\n
struct S {\n    S() { std::cout << "made an S" << std::endl; }\n    enum E { E1, E2 } member;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

(使用适当的#include)不再允许。Clang 和 g++ 产生不同的诊断。这是 clang 的抱怨:

\n\n
namespace.cc:8:3: error: no matching constructor for initialization of \'S\'\nS x = { .member = S::S::E1 };\nnamespace.cc:3:8: note: candidate constructor (the implicit copy constructor)\n      not viable: cannot convert argument of incomplete type \'void\' to\n      \'const S &\' for 1st argument\nstruct S {\n       ^\nnamespace.cc:3:8: note: candidate constructor (the implicit move constructor)\n      not viable: cannot convert argument of incomplete type \'void\' to \'S &&\'\n      for 1st argument\nstruct S {\n       ^\nnamespace.cc:4:3: note: candidate constructor not viable: requires 0 arguments,\n      but 1 was provided\n  S() { std::cout << "made an S\\n"; }\n  ^\n1 error generated.\n
Run Code Online (Sandbox Code Playgroud)\n\n

和 g++ 的:

\n\n
namespace.cc:8:28: error: could not convert \xe2\x80\x98{E1}\xe2\x80\x99 from \xe2\x80\x98<brace-enclosed initializer list>\xe2\x80\x99 to \xe2\x80\x98S\xe2\x80\x99\n S x = { .member = S::S::E1 };\n
Run Code Online (Sandbox Code Playgroud)\n\n

这些似乎遵循不同的规则。这里发生了什么?

\n\n

接下来,让我们尝试另一种滥用方式。这是整个程序:

\n\n
#include <iostream>\n\nstruct S {\n  S() { std::cout << "made an S\\n"; }\n  enum E { E1, E2 } member;\n};\n\nint main() {\n  std::cout << S::S::S::S::S::E1 << std::endl;\n#ifdef DECL\n  S::S::S var;\n#endif\n  return 0;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

此代码-DDECL在两个编译器中编译(不带 ):

\n\n
$ clang++-3.9 -std=c++11 -Wall -O namespace.cc\n$ ./a.out\n0\n$ g++ -Wall -std=c++11 -O namespace.cc\n$ ./a.out\n0\n
Run Code Online (Sandbox Code Playgroud)\n\n

(尽管在早期代码中S对变量初始值设定项发出了抱怨 clang 的抱怨,但此处并未构造No。)membermain会导致 g++ 失败,但 clang 不会:

\n\n
$ clang++-3.9 -std=c++11 -DDECL -Wall -O namespace.cc\n$ ./a.out \n0\nmade an S\n$ g++ -std=c++11 -DDECL -Wall -O namespace.cc\nnamespace.cc: In function \xe2\x80\x98int main()\xe2\x80\x99:\nnamespace.cc:11:3: error: \xe2\x80\x98S::S\xe2\x80\x99 names the constructor, not the type\n   S::S::S var;\n   ^\nnamespace.cc:11:11: error: expected \xe2\x80\x98;\xe2\x80\x99 before \xe2\x80\x98var\xe2\x80\x99\n   S::S::S var;\n           ^\nnamespace.cc:11:14: error: statement cannot resolve address of overloaded function\n   S::S::S var;\n              ^\n
Run Code Online (Sandbox Code Playgroud)\n\n

哪个编译器是正确的,为什么?这个“资格过高”的名字到底什么规则呢?

\n

小智 3

Yakk 已经解决了您问题的指定初始值设定项部分。我将解决你问题的最后一部分。S::S::S var(在这种情况下)有效吗?不,根据class.qual#2

在不忽略函数名称的查找中34并且嵌套名称说明符指定类 C:

  • 如果在 C 中查找时,在嵌套名称说明符之后指定的名称是 C 的注入类名称(子句 [class]),或者

  • 在作为成员声明的 using 声明的 using 声明符中,如果在嵌套名称说明符之后指定的名称与标识符或 simple-template-id 的最后一个组件中的模板名称相同嵌套名称说明符,

相反,该名称被视为命名类 C 的构造函数。

为了使其有效,您需要明确地说struct S::S::S var。所以 clang 3.9 是错误的。

此外,罗布的评论与此无关。S::S只是在类定义中查找时注入的类名。