没有枚举器的未命名枚举的标识

Bri*_*ian 7 c++ language-lawyer

考虑一个具有以下两个翻译单元的程序:

\n
// TU 1\n#include <typeinfo>\nstruct S {\n    enum { } x;\n};\nconst std::type_info& ti1 = typeid(decltype(S::x));\n\n\n// TU 2\n#include <iostream>\n#include <typeinfo>\nstruct S {\n    enum { } x;\n};\nextern std::type_info& ti1;\nconst std::type_info& ti2 = typeid(decltype(S::x));\nint main() {\n    std::cout << (ti1 == ti2) << \'\\n\';\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我用 GCC 和 Clang 编译它,在这两种情况下结果都是 1,我不知道为什么。(GCC 还警告“ISO C++ 禁止空的未命名枚举”,我认为这不是真的。)

\n

[dcl.枚举]/11指出,如果未命名枚举没有用于链接目的的 typedef 名称,但至少有一个枚举器,则它的第一个枚举器作为其名称用于链接目的。这些枚举没有枚举器,因此它们没有用于链接目的的名称。同一段还有以下注释,这似乎是未出于链接目的给出枚举名称的自然结果:

\n
\n

[注 3:每个没有枚举器的未命名枚举都是不同的类型。\xe2\x80\x94 尾注]

\n
\n

也许两个编译器都有一个错误。或者,更可能的是,我只是误解了这张纸条。无论如何,该注释是非规范性的,所以让我们看一些规范性的措辞。

\n

[基本.链接]/8

\n
\n

两个实体声明声明同一实体,如果考虑未命名类型的声明以引入其名称以用于链接目的(如果有的话)([dcl.typedef],[dcl.enum]),它们对应([basic.scope.scope]) ,具有相同的目标范围,但不是函数或模板参数范围,并且

\n
    \n
  • [无关]
  • \n
  • [无关]
  • \n
  • 他们都声明具有外部链接的名称。
  • \n
\n
\n

[基本范围.范围]/4

\n
\n

如果两个声明(重新)引入相同的名称、都声明构造函数或都声明析构函数,则它们对应,除非[不相关]

\n
\n

看来,当未命名的枚举没有出于链接目的而给出 typedef 名称,并且没有枚举器时,它在不同的翻译单元中不能与自身具有相同的类型。

\n

那么这真的只是一个编译器错误吗?我想到的最后一件事是,如果两个枚举类型确实不同,则 的多个定义S违反了单一定义规则,并使程序形成格式错误的 NDR。但我在 ODR 中找不到任何实际说明这一点的内容。

\n

Dav*_*ing 6

如所见,该程序格式良好并打印 1。因为S在具有外部链接的两个翻译单元中定义相同,所以就好像 ([basic.def.odr]/14) 有一个定义S,因此只定义了一种枚举类型。(实际上,它是根据名称S或被破坏的S::x。)

这与在内联函数的定义之间共享静态局部变量和 lambda 的现象相同:

// foo.hh
inline int* f() {static int x; return &x;}
inline auto g(int *p) {return [p] {return p;};}
inline std::vector<decltype(g(nullptr))> v;
Run Code Online (Sandbox Code Playgroud)
// bar.cc
#include"foo.hh"
void init() {v.push_back(g(f()));}
Run Code Online (Sandbox Code Playgroud)
// main.cc
#include"foo.hh"
void init();
int main() {
  init();
  return v.front()()!=f();  // 0
}
Run Code Online (Sandbox Code Playgroud)