奇数C++命名空间解析怪癖和 G++ 与 CLang++

odd C++ namespace resolution quirk and g++ vs clang++

本文关键字:G++ CLang++ C++ 命名空间 奇数      更新时间:2023-10-16

这始于一个观察。 我更改了一些看起来有点像这样的代码(编辑:我在这里删除了指定的初始值设定项,它们也不在原始代码中):

struct S {
enum E { E1, E2 } member;
}
// file1.cc
S v1 = { S::E1 };
// file2.cc
S v2 = { S::S::E2 };

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

S v3 = { S::S::S::S::S::S::S::E1 };

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

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

struct S {
S() { std::cout << "made an S" << std::endl; }
enum E { E1, E2 } member;
}

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

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

和 g++ 的:

namespace.cc:8:28: error: could not convert ‘{E1}’ from ‘<brace-enclosed initializer list>’ to ‘S’
S x = { .member = S::S::E1 };

这些似乎遵循不同的规则。 这是怎么回事?

接下来,让我们再尝试一点滥用。 这是整个程序:

#include <iostream>
struct S {
S() { std::cout << "made an Sn"; }
enum E { E1, E2 } member;
};
int main() {
std::cout << S::S::S::S::S::E1 << std::endl;
#ifdef DECL
S::S::S var;
#endif
return 0;
}

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

$ clang++-3.9 -std=c++11 -Wall -O namespace.cc
$ ./a.out
0
$ g++ -Wall -std=c++11 -O namespace.cc
$ ./a.out
0

(尽管在早期代码中为变量member初始值设定项发出了投诉 clang,但此处没有构造任何S。 但是,在main中启用变量会导致 g++ 失败,但不会使用 clang 失败:

$ clang++-3.9 -std=c++11 -DDECL -Wall -O namespace.cc
$ ./a.out 
0
made an S
$ g++ -std=c++11 -DDECL -Wall -O namespace.cc
namespace.cc: In function ‘int main()’:
namespace.cc:11:3: error: ‘S::S’ names the constructor, not the type
S::S::S var;
^
namespace.cc:11:11: error: expected ‘;’ before ‘var’
S::S::S var;
^
namespace.cc:11:14: error: statement cannot resolve address of overloaded function
S::S::S var;
^

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

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

在不忽略函数名称34和 嵌套名称说明符指定一个类 C:

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

  • 在作为成员声明的 using-声明符中,如果在 嵌套名称说明符与标识符或 简单模板 ID 的模板名称在 嵌套名称说明符,

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

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

此外,Rob的评论在这里无关紧要。S::S只是在类定义中查找时注入的类名。

指定的初始值设定项是在 c++2a 中C++的 C 功能。 它们是C++模式下的常见扩展。 显然,在这一点上,它们仅限于 pod(类 C)类型。 毫不奇怪,他们打破了。

通常,不同的编译器可能会为不正确的代码产生不同的错误,尤其是在使用C++扩展时。 C++标准从未强制要求诊断的内容(可能有例外,但我不记得是什么)。

GCC 有一个扩展,可以让你命名对象构造函数。 这与结构命名空间的病理使用相冲突。