I am reading Bjarne Stroustrup's "Tour of C " (2:nd edition). In chapter 2.5, he discusses enums with the following example:
enum class Color {red,blue,green};
In the same chapter, he says that it is allowed to initialize an enum with a value from its underlying type (int by default), and gives the following example:
Color x = Color{5}; //OK, but verbose
Color y {6}; //Also OK
I tested, and this certainly compiles. However, I am a bit confused about the meaning of initializing an enum with 5 or 6, which falls outside the "range" (0-2) of Color. What are actually the values of x and y in the above example? By testing, it does not appear to be either "red", "green" or "blue".
As a follow-up question, why is this "out-of-range" initialization of enums allowed? It does not seem very sensible.
Note that integer initialization of enums is not supported in C 11, but only in C 17 and forward.
CodePudding user response:
This comes from C: enumerated types are fancy integers. The names of the enumerations are handy, but they don't define every value that the type can represent.
Probably the most common use for an enumerated type is, though, as a simple list of constants:
enum state [
off,
starting,
running,
shutting_down,
out_of_service
};
The values of these enumerators are off
== 0, starting
== 1, running
== 2, shutting_down
== 3, and out_of_service
== 4.
But, formally, you aren't restricted to those values. And sometimes there are interesting combinations of values:
enum color {
red = 0b0001,
green = 0x0010,
blue = 0x0100
};
The values of these enumerators have been carefully contrived so that each one uses a different bit; that lets us combine values easily.
For example, the color "purple" is a combination of red and blue. Combining the bits for red (0b0001) and blue (0b0100) gives us a value with two bits set: 0b0101. That's the value 5, and it's not one of the named values. But it's okay to store that value:
color x = red | blue; // x represents the value of purple
std::cout << x << '\n'; // displays 5
(I'm not compiling this, since I don't have a compiler on this laptop, so you might need a cast here)
Similarly,
color y = red | green; // y represents the value of yellow
std::cout << y << '\n'; // displays 3
The rule for what values can be stored is a bit complicated, but basically, if the bits are there, you can use them.
Doing this obviously lets you store values that represent multiple things, but it also lets you query those things:
bool has_blue(color c) { return c & blue; }
assert(has_blue(x));
assert(!has_blue(y));
So, basically, this lets you use a named type rather than a generic integer type when you need to store and query numeric fields. Sure, you could do all this with ordinary constants:
const int red = 0x0001;
const int green = 0x0010;
const int yellow = 0x0100;
and combine them as integer values:
int x = red | blue;
int y = red | green;
but now the type of each of these is just a plain old int
, not nearly as descriptive as calling it color
.
CodePudding user response:
why is this allowed
Well, one of the fundamental design goals of C (and i will probably get this quote wrong) was that Bjarne "didn't know what problems people needed to solve" and therefore designed the language to allow you to violate its guard rails.
As to when this might be useful, some times you want something to be in a combined state:
enum Color
{
red = 1 << 0,
blue = 1 << 1,
green = 1 << 2
};
So the valid value of 5 is greater than the highest labeled value of the enum (4), and would represent something both green and red.