5

Why isn't this valid C++?:

enum foo : unsigned { first_foo, second_foo };
enum bar : foo { best_foo = first_foo };

GCC 5.4.0 says:

/tmp/a.cpp:3:16: error: underlying type ‘foo’ of ‘bar’ must be an integral type
     enum bar : foo { best_foo = first_foo };

I can understand why I would get this error if foo were a float, or some struct, or what-not. But this seems perfectly legit to me in terms of semantics, type safety etc. What am I missing?

ildjarn
  • 62,044
  • 9
  • 127
  • 211
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • 2
    Why? Because standard says so. :) – Zereges Nov 15 '16 at 10:18
  • 1
    @Zereges: [But... why?](https://cdn.meme.am/cache/instances/folder489/500x/59809489.jpg) – einpoklum Nov 15 '16 at 10:19
  • 1
    The usual reason is that [some people wrote a proposal](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2347.pdf) and what they proposed was accepted. – Bo Persson Nov 15 '16 at 10:43
  • For me it is a logical error to say a enum can be based on an another enum. If you think that the base is the storage type where the values defined in the enum are stored in, I can not get it as a logic expression to say the storage type is an enum. Because the enum is not only the storage type, it also contains a set of valid values. So what should be an enum based on an other enum be?Only a subset of the underlaying enum, because all other values are not defined in the underlaying one? Why should a enum not be based on a bitfield which also represents some kind of integral storage type? – Klaus Nov 15 '16 at 10:57
  • @Klaus: See my motivation [here](http://stackoverflow.com/q/40607928/1593077). But you make a valid point, perhaps you should make that an answer. – einpoklum Nov 15 '16 at 10:59
  • Inheritance would be a horrible feature. Will a variable of type `enum bar` be convertible to type `enum foo`? Or vice versa? How does this interact with `enum class`? How does this complicate `std::common_type`? What is one use case of this feature? – KevinZ Nov 21 '16 at 19:25
  • @KevinZ: You already have a link to my motivation. And - this is not really inheritance, it's base-typing. And `bar`s are always `foo`s but not vice-versa. I don't think it complicates common_type mor than non-enum base-type'ing (although I could be wrong). – einpoklum Nov 21 '16 at 21:09

4 Answers4

10

C++11 [dcl.enum]/2:

The type-specifier-seq of an enum-base shall name an integral type; any cv-qualification is ignored.

Enums are not themselves integral types – [basic.fundamental]/7:

Types bool, char, char16_t, char32_t, wchar_t, and the signed and unsigned integer types are collectively called integral types.

This is accompanied by a non-normative footnote:

Therefore, enumerations are not integral; however, enumerations can be promoted to integral types as specified in [conv.prom].

To achieve the effect I think you're looking for, however, is still simple:

enum bar : std::underlying_type<foo>::type { best_foo = first_foo };
ildjarn
  • 62,044
  • 9
  • 127
  • 211
  • Ok, that's the immediate formal reason - but why must type-specifier-seq name an integral type rather than either an integral type or an enum type? – einpoklum Nov 15 '16 at 10:16
  • @einpoklum : That's a far-reaching question for this sort of site. I'm not on the C++ committee, so I'm not sure how I could know. ;-] – ildjarn Nov 15 '16 at 10:17
  • Well, sometimes people who are either on the committee or following its work frequent this site and answer questions. Bjarne even answered me once. – einpoklum Nov 15 '16 at 10:18
  • @einpoklum : I'm not saying you can't luck out, but if you _expect_ that rank of answer you're most likely going to be disappointed. – ildjarn Nov 15 '16 at 10:21
  • 1
    @einpoklum I suspect the answer is that they didn't deem it important. If you want it to be changed, write a proposal :). – TartanLlama Nov 15 '16 at 10:26
  • 1
    Inspired by this answer, I check the syntax of integral types. http://en.cppreference.com/w/cpp/language/type [ integral types (see also std::is_integral)] – ctc chen Nov 15 '16 at 10:53
5

When you add things to C++, you tend to add the minimium amount that solves a problem.

The enum A:int syntax lets you specify exactly how the enum A is stored as an integer. That is all it does, and it solves the problem.

enum A:B where B is an enum could have many meanings. A could extend B, or A could be a subset of the B underlying type with different names, or A could be an enum strictly restricted to have a subset of the values that can be stored within B, or A could be an enum whose values are restricted to the "hull" of B values.

Of these, the "same underlying type" solution (with the :int syntax) lines up with enum A:std::underlying_type_t<B>. So there is already a way to do that, and it isn't particularly burdensome.

Until someone makes a suggestion for what enum A:B should mean and convinces enough of the committee, it is unlikely to be permitted. And when there are multiple different reasonable meanings, this makes it harder for any one meaning to be chosen. One would need a strong use case why one particular meaning was best, and why it is worth the effort to put it in the standard.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Well, then, if you like - you could have a look at my [use case](http://stackoverflow.com/q/40607928/1593077) and chime in on whether/to what extent you find it compelling. – einpoklum Nov 15 '16 at 16:38
3

Well, not exactly the same, but I think you could use std::underlying_type:

enum foo : unsigned { first_foo, second_foo };
enum bar : std::underlying_type_t<foo> { best_foo = first_foo };
Bartek Banachewicz
  • 38,596
  • 7
  • 91
  • 135
  • 2
    @einpoklum Well, you didn't mention that in your question, so how could I possibly know? I don't know why it doesn't satisfy your needs, either; the regular `enum` is pretty weak when it comes to underlying type conversions anyway. – Bartek Banachewicz Nov 15 '16 at 10:10
3

For me it is a logical error to say a enum can be based on an another enum. If you think that the base is the storage type where the values defined in the enum are stored in, I can not get it as a logic expression to say the storage type is an enum. Because the enum is not only the storage type, it also contains a set of valid values.

If we can write something like:

enum A: int { ONE, TWO };

what should mean:

enum B: A{};

Because A defines not only the underlaying type ( I call this the storage type ) but also a set of valid values, should B only a subset of the enum A which means that you can define only values already defined in A?

Is it now valid to say we habe the values ONE,TWO already defined also in B? And is it now possible to add more values like:

    enum B: A{THREE};

and all valid values are now ONE,TWO,THREE ?

or is the meaning we get only the subset:

    enum B: A{ONE};

which means B can only use values already defined in A. Simply that makes it difficult to me to make a enum a base of another enum.

If you open that door, you also can also come to the idea that you want to use the underlaying storage type from other kinds of types like bitfields.

struct A { unsigned int a : 3; unsigned int B: 2; };

should then

enum B: A { ONE, TWO };

also be valid? I believe not! ;)

From the logical point ( my point of view ) the underlaying type of the enum is its storage type. And it makes no sense to make an enum as an underlaying storage type for another enum at all.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • 1
    "should Be only be a subset of the enum A"? Yes, I would think so actually. – einpoklum Nov 15 '16 at 12:20
  • @einpoklum If it can only be a subset, it is the opposite of deriving from a struct/class. Because you can only extend a base class in the derived one. So is it logical ok for you to reverse the logic for enums? ;) – Klaus Nov 15 '16 at 12:23
  • Klaus: That's not true. Inheritance is often used to constrict the base class rather than expand it. You have both "Is-a-kind-of which-also" and "is-a-kind-of which-only" relations between inherited classes. – einpoklum Nov 15 '16 at 15:36