llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
28.93k stars 11.94k forks source link

[C++] Accessing enumerators from a scoped member enum through operator `.` triggers an error #113844

Open TheCalligrapher opened 1 week ago

TheCalligrapher commented 1 week ago

Here's the code sample that demonsrates the problem

struct S
{
  enum T { A };
  enum class U { B };
};

int main()
{
  S s;

  S::A;    // OK
  S::T::A; // OK
  S::U::B; // OK

  s.A;     // OK
  s.T::A;  // OK
  s.U::B;  // Error???
}

All six lines that refer to enumerators from enums declared inside class S are perfectly valid. However, Clang issues an error

error: 'S::U::B' is not a member of class 'S'
   17 |   s.U::B;  // Error???
      |     ~~~^

for last one (i.e. s.U::B). Why?

What makes it especially weird is that Clang has no problems with the qualified name in the s.T::A line, i.e. it does allow one to optionally use a qualified name to access enumerators from a "classic" unscoped enum (using qualified name with such enums is a possibility since C++11). However, for some unknown reason Clang rejects a similar attempt to access an enumerator from a scoped enum.

GCC and MSVC++ have no issues with such code.

keinflue commented 1 week ago

According to [class.mem.general]/3 only enumerators of unscoped enumerations are members of the class.

[expr.ref]/6.5 establishes the meaning of the member access expression only for member enumerators.

However, [expr.ref]/6 also claims "Otherwise, one of the following rules applies." which doesn't seem to be true here. That looks like a defect to me. The suggested resolution for CWG 2902 would however clarify that this is ill-formed (because U::B is not a member of the class).

llvmbot commented 1 week ago

@llvm/issue-subscribers-clang-frontend

Author: AndreyT (TheCalligrapher)

Here's the code sample that demonsrates the problem ``` struct S { enum T { A }; enum class U { B }; }; int main() { S s; S::A; // OK S::T::A; // OK S::U::B; // OK s.A; // OK s.T::A; // OK s.U::B; // Error??? } ``` All six lines that refer to enumerators from enums declared inside class `S` are perfectly valid. However, Clang issues an error ``` error: 'S::U::B' is not a member of class 'S' 17 | s.U::B; // Error??? | ~~~^ ``` for last one (i.e. `s.U::B`). Why? What makes it especially weird is that Clang has no problems with the qualified name in the `s.T::A` line, i.e. it does allow one to optionally use a qualified name to access enumerators from a "classic" unscoped enum (using qualified name with such enums is a possibility since C++11). However, for some unknown reason Clang rejects a similar attempt to access an enumerator from a _scoped_ enum. GCC and MSVC++ have no issues with such code.
zygoloid commented 1 week ago

Note that the interpretation of the rules that allows the final example would also allow:

enum class E { a };
class B {} b;
E e = b.E::a;

(indeed that's basically the same thing as far as the language is concerned). [expr.ref]/7.5 doesn't say what happens here, for an E2 that is a non-member enumerator, so at best this is UB. But it seems appropriate to reject, as CWG2902's suggested resolution says.

TheCalligrapher commented 1 week ago

Oh, I see. Thank you for the clarification. [class.mem.general]/3 does indeed make formal sense, even if the behavior might look "illogical" on the surface.

Sorry for the false alarm.

TheCalligrapher commented 2 days ago

... However, it is possible to "dump" the enumerators from a scoped enum into the class scope by means of a using enum declaration

struct S
{
  enum class U { B };
  using enum U;
};

With this combination of declarations Clang happily accepts s.B syntax

S s;
s.B; // OK

which is not surprising, since this is basically the example from [enum.udecl].

But if I try referring to that B through a qualified name after the dot operator (as U::B or S::U::B) Clang issues an error

S s;
s.U::B;    // error: 'S::U::B' is not a member of class 'S'
s.S::U::B; // error: 'S::U::B' is not a member of class 'S'

Of course, this is a weird code, but still... are both of these really supposed to be erroneous? (GCC appears to accept all these variants.)

keinflue commented 2 days ago

I do not see why the earlier reasoning shouldn't apply to s.B; as well. B is still not a member of the class and so this should also be ill-formed (or maybe technically UB as @zygoloid mentioned).

This seems to be an editorial issue in the standard. I have reported it here.