enum class

Hi,

In the book the tour of C++ 2nd edition page 45, Stroustrup says:

1
2
enum class Color {red, blue, green};
	Color y {6}; // OK 


But in practice, I get the initialization error!
Last edited on
I think this was added in C++17. I don't understand the logic behind this change to be honest.
My Visual Studio compiler is also 2017. So why do I still get the error please?
And what do you mean by " logic behind this change to be honest"?
My Visual Studio compiler is also 2017. So why do I still get the error please?

It seems you need at least Visual Studio 2017 15.3 for this "feature".

And what do you mean by " logic behind this change to be honest"?

I mean that I don't understand why they want to allow this. Personally I want to be able to program under the assumption that an enum can only be one of the listed enumerator values, and I wouldn't be against the compiler using this assumption to make its own optimizations, but this change seems to move in the opposite direction.

I think it's also inconsistent because {} is normally very picky in what conversions it allows.

1
2
3
4
5
6
short a = 2;
short b = 3;
short c {a + b}; // error, narrowing conversion (but a perfectly sensible thing to want to do)

enum class Color {red, blue, green};
Color y {6}; // no error (but doesn't make much sense) 
Last edited on
What does that even do? Does the compiler automatically assign to the enum values integer values that are powers of two? Weird.
No, it just assigns to the underlying value, bypassing the type safety that even unscoped enums used to enforce in this situation. Before C++17 you had to use static_cast do the same thing (and I don't see why that was a bad thing).

 
Color y = static_cast<Color>(6);

Now you have ended up with a color that is neither red, blue or green.
Last edited on
Oh. Ew. I thought (part of) the point of enum classes was that they were more strongly typed than enums. This seems kind of "two steps forward, one step back".
We now have this unfortunate inconsistency in the language where {} is less permissive than (), except when it comes to enums.

1
2
3
4
5
Color c1 { 5 } ; // OK 
Color c2 ( 5 ) ; // Error

int i1 { 1.7 } ; // Error
int i2 ( 1.7 ) ; // OK 
Last edited on
Yeah, "neither red, blue or green."
My VS ver is 15.9.6 but still get error for Color y {6};!

And if it were OK, what value would be the content of y there?

and I think in all cases where both () and {} are valid, {} is more strict.
I wrote:
We now have this unfortunate inconsistency in the language ...

After some thought I realize it's not as inconsistent as it first seems. It's not a narrowing conversion (because no information is lost). If you think of an enum as a type that contains an integer it suddenly becomes consistent with how the initialization of aggregates work.

1
2
3
4
5
6
7
enum class EColor {};
EColor ec1 { 5 } ; // OK 
EColor ec2 ( 5 ) ; // Error

struct SColor { int value; };
SColor sc1 { 5 } ; // OK 
SColor sc2 ( 5 ) ; // Error 

So maybe I should drop the inconsistency argument. I still think it's questionable whether this conversion should be allowed to be implicit, with any initialization syntax.
Last edited on
My VS ver is 15.9.6 but still get error for Color y {6};!

Some new features are not enabled by default. You might have to use the /std:c++17 (or /std:c++latest) compiler flag.

And if it were OK, what value would be the content of y there?

The value is stored as an int. Color::red is represented by the value 0, Color::blue by the value 1, and Color::green by the value 2. In your example y would just hold the value 6 even though it doesn't correspond to any of the enumerator constants. Note that these rules apply to scoped enumerations (enum class), not necessarily to unscoped enumerations (enum).

I think in all cases where both () and {} are valid, {} is more strict.

It depends on what we mean by "strict". {} can be used to initialize aggregates and to call std::initializer_list constructors, something that () cannot do. See my previous post.
Last edited on
they add all that and still can't print the text value of the constant and have to roll your own mess to do that.
closed account (E0p9LyTq)
This might be yet another VS vs. GCC implementation issue.

For instance: VLAs error out in VS at default settings, yet are allowed with warnings in GCC.

http://coliru.stacked-crooked.com/a/2a4b0f278255aeec
Last edited on
@Peter87:
Thank you. I set that to /std:c++latest and it worked. But should I do that for each project individually? I mean, isn't there anyway to set it to default to be used for all C++ projects?

Note that these rules apply to scoped enumerations (enum class), not necessarily to unscoped enumerations (enum).


You mean that there we could assign a number, 6, which is not amongst the enum Color enumerators (0, 1, 2) to y because the enum was declared scoped, and if it were declared unscopped we would have to pick one number exactly among the defined package (enumerator constants). Right?


1
2
struct SColor { int value; };
SColor sc1 { 5 } ; // OK  


Here I don't assume sc1 is an object where its int value is 5, because even if the compiler creates a default constructor for us, in the absence an explicit constructor, there's not any instruction to put the gotten value (5) into that int value of the struct.
I set that to /std:c++latest and it worked. But should I do that for each project individually?

I don't know, I don't use Visual Studio, but I wouldn't be surprised if you can at least set the default options to be used for new projects, but that is just a guess.


Note that these rules apply to scoped enumerations (enum class), not necessarily to unscoped enumerations (enum).
You mean that there we could assign a number, 6, which is not amongst the enum Color enumerators (0, 1, 2) to y because the enum was declared scoped, and if it were declared unscopped we would have to pick one number exactly among the defined package (enumerator constants). Right?

If the underlying type is not "fixed" the result is undefined if you assign a value that is larger than what can be stored in the smallest bit field that are able to store all the enumerators. In this case the largest enumerator value is 2, which requires at least 2 bits to be stored. The largest possible value that you can store in 2 bits is 3, which means you would be able to store the values 0, 1, 2, 3, but not 4 and above.

The only situation when the underlying type is not fixed (and the above rules apply) is when you use an unscoped enum without specifying the underlying type. Scoped enums uses int as the underlying type by default so they always have a fixed underlying type. If the underlying type is fixed it means you can safely store any value that can be represented by the underlying type so that is why it is possible to store 6 in Color because 6 is small enough to be stored in an int.


1
2
struct SColor { int value; };
SColor sc1 { 5 } ; // OK   
Here I don't assume sc1 is an object where its int value is 5, because even if the compiler creates a default constructor for us, in the absence an explicit constructor, there's not any instruction to put the gotten value (5) into that int value of the struct.

It creates a SColor object with the value member initialized to 5.

This type of initialization is called aggregate initialization. It's only possible if the class fulfills the requirements for being an aggregate (only public data members, no user-declared constructor, no virtual member functions, etc.).

https://en.cppreference.com/w/cpp/language/aggregate_initialization
Last edited on
Topic archived. No new replies allowed.