5

First part:

I'm studying in some detail opaque-enum-declarations and elaborated-type-specifiers for a few days already, and I really would like somebody to confirm this. GCC and VS2013 don't compile this code (clang does) and I believe clang is in accordance with §7.1.6.3/1, as enum E is an elaborated-type-specifier that is not the sole constituent of the declaration enum E e = E::b;. Is my analysis correct?

#include <iostream>
enum class E : char {a = 'a', b};
int E;
enum E e = E::b;        // Doesn't compile in GCC and VS2013
int main()
{
    std::cout << (char)(e) << '\n';
}

Second part:

The snippet below, which is very similar to the one above, doesn't compile. I understand why it doesn't (the elaborated-type-specifier enum E is the sole constituent of the declaration enum E; and §7.1.6.3/1 doesn't allow this). What I'd like to know is why can't the compiler accept this construction?

#include <iostream>
enum class E : char {a = 'a', b};
int E;
enum E;                 // This doesn't compile.
E e = E::b;        
int main()
{
    std::cout << (char)(e) << '\n';
}
Belloc
  • 6,318
  • 3
  • 22
  • 52
  • 2
    For the second part, making `enum class E : char {}; enum E; ` valid but not `enum E; enum class E : char{};` would be quite...odd. [N2764](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2764.pdf) also has some relevant discussion. – T.C. Mar 20 '15 at 22:05
  • 2
    This is [GCC bug#59921](https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59921). – Casey Mar 20 '15 at 22:10
  • @T.C. Why odd? `enum E; enum class E: char{};` cannot be valid because of §3.4.4/1. That is, an *elaborated-type-specifier*, like `enum E` would be, is used to refer to a previous enum name, which wouldn't exist in this case. – Belloc Mar 20 '15 at 22:58
  • It's inconsistent with the behavior of forward declarations of classes as well as *opaque-enum-declaration*s. And limited to the redeclaration case it doesn't really add anything useful enough to justify the extra complexity ("`enum E;` is an error, except when `E` has been previously declared to be an enumeration, in which case it is a no-op" is not very teachable or useful). – T.C. Mar 20 '15 at 23:37
  • @T.C. But the example refers to an elaborated-type-specifier. It has nothing to do with forward declaration. §3.4.4/1 is clear about this. That's why I inserted the `int E;` declaration in the code. As far as I can understand there must be another reason for the spec to disallow the declaration `enum E;` in the second snippet above. – Belloc Mar 21 '15 at 00:22
  • The whole point of allowing an elaborated-type-specifier to be used like `class A;` is that it allows you to forward declare things. What, exactly, would the declaration `enum E;` do? – T.C. Mar 21 '15 at 00:42
  • As for forward declaring enums please see [Why must an enumeration's size be provided when it is forward declared?](http://stackoverflow.com/q/29035225/1708801) as well which covers the pointer reference case specifically which is actually very interesting case. cc @T.C. – Shafik Yaghmour Mar 21 '15 at 03:08
  • @T.C. `The whole point of allowing an elaborated-type-specifier to be used like class A; is that it allows you to forward declare things.` I have to disagree. Look at my example (first part): `enum E` is an elaborated type specifier and it can be used to declare an enum object `e` in the declaration `enum E e = E::b;`. Consider now a forward declaration of a class A: `class A;`. If you try to declare an object of class A just after this declaration, it will not compile. (to be continued) – Belloc Mar 21 '15 at 11:29
  • @T.C. For me the two concepts are different. An elaborate-type-specifier will always start a lookup for a previous declaration, whereas in a forward declaration, this doesn't happen. – Belloc Mar 21 '15 at 11:29
  • @ShafikYaghmour I must insist, I'm not talking about forward declarations in my examples, but about an elaborated type specifier. In summary, I don't understand why you can use `class A; A a;` where `class A` is an elaborated type specifier, but you can't use `enum E; E e;`. (to be continued) – Belloc Mar 21 '15 at 12:20
  • @ShafikYaghmour Now in relation to a forward declaration, it's still not clear to me why this is OK: class A; void f(A*); but this is ill-formed: `enum E; void f(E*);` . See the comments to [this answer](http://stackoverflow.com/a/71495/1042389) in SO. – Belloc Mar 21 '15 at 12:22
  • @Belloc maybe I am missing something but I think this is follows from the details in my [answer](http://stackoverflow.com/a/29035972/1708801) since enums size can vary unless as an extension the compiler defaults enums to a particular size all the issue I mention in my answer apply. Visual Studio seems to apply this extension but is definitely not portable. – Shafik Yaghmour Mar 21 '15 at 16:20
  • 1
    I'm not sure I'm understanding your point. `class A` is an *elaborated-type-specifier*. When you write `class A;`, a lookup *always* happens for `A`; if it is found to be a type, then this is a redeclaration; if it's not found, it's a forward declaration. But allowing you to redeclare things already declared before, by itself, is not useful. The main benefit of allowing the `class A;` syntax is that it can be used to forward declare things, a benefit that does not exist with `enum E;` as Shafik explained in the linked question. – T.C. Mar 21 '15 at 19:21
  • @T.C. I've just realized there's more that I need to know about an *elaborated-type-specifier* then I thought when I started this discussion. I'll come back later to address your comment. – Belloc Mar 22 '15 at 13:14
  • I take back my third sentence. `class A;` doesn't cause an unqualified name lookup, but instead unconditionally introduces the name `A` into the current scope as a *class-name*. Such a declaration can still be either a forward declaration or a redeclaration, depending on whether `A` has already been declared in the current scope. The rest of that comment still holds - the main benefit of allowing `class A;` is to allow `A` to be forward declared. – T.C. Mar 23 '15 at 02:39

1 Answers1

7

N4140 [basic.scope.hiding]/2:

A class name (9.1) or enumeration name (7.2) can be hidden by the name of a variable, data member, function, or enumerator declared in the same scope. If a class or enumeration name and a variable, data member, function, or enumerator are declared in the same scope (in any order) with the same name, the class or enumeration name is hidden wherever the variable, data member, function, or enumerator name is visible.

It would appear the the declaration of int E hides the name of the enumeration E in the global scope after the int's point of declaration. However, the name E::b is a qualified-id with nested-name-specifier of E::, so the rules for qualified name lookup apply. In particular, [basic.lookup.qual]/1:

The name of a class or namespace member or enumerator can be referred to after the :: scope resolution operator (5.1) applied to a nested-name-specifier that denotes its class, namespace, or enumeration. If a :: scope resolution operator in a nested-name-specifier is not preceded by a decltype-specifier, lookup of the name preceding that :: considers only namespaces, types, and templates whose specializations are types. [emphasis added.] If the name found does not designate a namespace or a class, enumeration, or dependent type, the program is ill-formed.

So clang is conforming, GCC and MSVC are not.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • Isn't the lookup for the first `E`, in the declaration `enum E e = E::b;` done before a possible lookup for the second `E`? If that is the case, I would say that the explanation for the first code to compile is more related to §7.1.6.3 and §3.3.4/1 than to [basic.scope.hiding]/2. – Belloc Mar 20 '15 at 22:49
  • @Belloc `auto e = E::b;` behaves in the same way. – Anton Savin Mar 20 '15 at 22:51
  • @AntonSavin I believe they are not the same, as far as the lookups are concerned. In your case, I have no doubt that the explanation given by Casey is correct, as there's no elaborated-type-specifier involved. – Belloc Mar 20 '15 at 23:03
  • @Belloc But GCC is complaining about the second `E`, not the first. – T.C. Mar 20 '15 at 23:38
  • @T.C. Perfect! What else can I say? – Belloc Mar 21 '15 at 00:11
  • Great answer (+1). I was convinced that GCC was failing to comply with §7.1.6.3/1, but T.C.'s comment above was pretty clear. – Belloc Mar 21 '15 at 00:26
  • For the second part see my [comment above](http://stackoverflow.com/questions/29176179/%C2%A77-1-6-3-1-c14-doesnt-accept-the-construction-in-the-second-snippet-below#comment46574671_29176179) – Shafik Yaghmour Mar 21 '15 at 03:16