Identity of unnamed enums with no enumerators

Consider a program with the following two translation units:

// TU 1
#include <typeinfo>
struct S {
    enum { } x;
};
const std::type_info& ti1 = typeid(decltype(S::x));


// TU 2
#include <iostream>
#include <typeinfo>
struct S {
    enum { } x;
};
extern std::type_info& ti1;
const std::type_info& ti2 = typeid(decltype(S::x));
int main() {
    std::cout << (ti1 == ti2) << 'n';
}

I compiled it with GCC and Clang and in both cases the result was 1, and I’m not sure why. (GCC also warns that “ISO C++ forbids empty unnamed enum”, which I don’t think is true.)

[dcl.enum]/11 states that if an unnamed enumeration does not have a typedef name for linkage purposes but has at least one enumerator, then it has its first enumerator as its name for linkage purposes. These enums have no enumerators, so they have no name for linkage purposes. The same paragraph also has the following note which seems to be a natural consequence of not giving the enums names for linkage purposes:

[Note 3: Each unnamed enumeration with no enumerators is a distinct type. — end note]

Perhaps both compilers have a bug. Or, more likely, I just misunderstood the note. The note is non-normative anyway, so let’s look at some normative wording.

[basic.link]/8

Two declarations of entities declare the same entity if, considering declarations of unnamed types to introduce their names for linkage purposes, if any ([dcl.typedef], [dcl.enum]), they correspond ([basic.scope.scope]), have the same target scope that is not a function or template parameter scope, and

  • [irrelevant]
  • [irrelevant]
  • they both declare names with external linkage.

[basic.scope.scope]/4

Two declarations correspond if they (re)introduce the same name, both declare constructors, or both declare destructors, unless [irrelevant]

It seems that, when an unnamed enum is not given a typedef name for linkage purposes, and has no enumerators, it can’t be the same type as itself in a different translation unit.

So is it really just a compiler bug? One last thing I was thinking is that if the two enum types really are distinct, then the multiple definitions of S violate the one-definition rule and make the program ill-formed NDR. But I couldn’t find anything in the ODR that actually says that.

Answer

This program is well-formed and prints 1, as seen. Because S is defined identically in both translation units with external linkage, it is as if there is one definition of S ([basic.def.odr]/14) and thus only one enumeration type is defined. (In practice it is mangled based on the name S or S::x.)

This is just the same phenomenon as static local variables and lambdas being shared among the definitions of an inline function:

// 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;
// bar.cc
#include"foo.hh"
void init() {v.push_back(g(f()));}
// main.cc
#include"foo.hh"
void init();
int main() {
  init();
  return v.front()()!=f();  // 0
}