Enumerator Scope and Qualification
A plain enum is an enumeration whose enumerators are entered into the same scope as the enum name, and which implicitly converts to integers.
Predicting What a Plain Enumerator Stores
Sometimes a program needs a fixed set of named choices, such as the colors it understands, and it is convenient for each choice to be usable as a plain name rather than a qualified one. C++ enumerations provide that tool. Before any rules are introduced, read the two lines below once and do not run them.
What do you think happens?
After the second line runs, what value does col hold?
Reveal answer
1
The name green is written with no enum name in front of it, and it is an enumerator of a plain enum. Plain enumerators implicitly convert to their integer value. Green is the second enumerator in the list, and counting starts at 0, so green has the value 1 and col receives 1. No cast is written.
The rest of this article explains the two rules that produce that result: where the names of a plain enum are visible, and how its integer values are counted. Keep the answer you chose in mind, because the explanation will show whether your prediction matched the rules.
What Makes an Enum Plain
A plain enum is an enumeration whose enumerators are entered into the same scope as the enum name, and which implicitly converts to integers.
A plain enum is created by writing the enum keyword, then a name, then a braced list of enumerators. The plain form is obtained from the scoped form by removing the word class from enum class. That single change trades explicit qualification and conversion for convenience. The scoped form requires you to write the enum name before an enumerator, while the plain form does not. The plain form also converts an enumerator to an integer automatically, while the scoped form does not.
So a plain enum makes two choices, and both are the looser setting. First, enumerator names do not have to be qualified. Second, enumerator values behave as integers without an explicit conversion. Those two properties are what the word plain refers to in this article.
Where Plain Enumerator Names Are Visible
The enumerators of a plain enum are placed in the same scope as the enum's own name. In the Color example, the name Color and the names red, green, and blue all belong to the scope where the enum is declared. No extra qualifier is needed to reach an enumerator from that scope. A name such as green can be written directly in the code that uses it.
The same placement that makes plain enums convenient also has a cost. Enumerator names share the surrounding scope with everything else declared there. This is part of why plain enums are described as less well behaved than their scoped counterparts. A name introduced by an enum can collide with a variable, a function, or another enumerator that already occupies the same scope.
- Color and Light both declare names in the same scope, and every enumerator of each is visible without a qualifier.
- green is used directly, with no enum name in front of it.
- Because the names are all in one scope, a second enum that also declared green would conflict with the first.
Counting Integer Values
The integer a plain enumerator converts to is determined by its position in the list. By default, integer values start at 0, and each additional enumerator has a value one greater than the enumerator before it. The first enumerator in the list has value 0, the next has value 1, and so on through the list in order.
Predicting the value of blue
Given enum Color { red, green, blue };, what integer does blue convert to?
- 1Identify the position
The enumerators appear in the order red, green, blue. Blue is third in the list.
- 2Start counting at zero
The first enumerator, red, has value 0.
- 3Add one for each step
Green is one greater than red, so green is 1. Blue is one greater than green, so blue is 2.
blue converts to 2.
An enumerator can also be given an explicit value. When it is, the enumerators after it continue counting from that value rather than from the default. The following generated example shows that behavior.
m is 6 and h is 7- low is given the explicit value 5.
- medium has no explicit value, so it is one greater than low, which is 6.
- high is one greater than medium, which is 7.
Tracing col Through the Assignment
Now trace the state of the program from the opening example. Before the assignment, col has no value that you can rely on. The assignment reads green, which is a plain enumerator, and the implicit conversion turns it into its integer value. The variable then holds that integer.
enum Color { red, green, blue };
int col;col is declared but has no assigned value
enum Color { red, green, blue };
int col = green;col holds 1, the integer value of green
No cast appears anywhere in the assignment. The conversion happens because an enumerator appearing where an integer is needed is converted to its integer value. The stored value is the position-based value from the counting rule, so the only thing you need to know to predict col is the position of green in the list.
Plain Enum Versus enum class
Comparing the two forms makes the design choice clear. The plain form gives you convenience, and the scoped form gives you explicit qualification and no implicit conversion to int.
| Property | Plain enum | enum class |
|---|---|---|
| Declaration | enum Color { red, green, blue }; | enum class Color { red, green, blue }; |
| Enumerator names | Visible in the enclosing scope without qualification | Require the enum name before the enumerator |
| Conversion to int | Implicit; no cast written | Not implicit; the source contrasts this with the plain form |
| Main trade-off | Convenience, with names sharing the surrounding scope | Explicit qualification and conversion |
Two properties separate a plain enum from its scoped counterpart.
Plain enums remain common in current code, and they have been part of C++ and C since the earliest days of both languages. When you read existing C++ code, you should expect to meet them, so understanding their two properties is practically useful even when you would choose the scoped form for new code.
Mistakes With Plain Enum Code
Common mistake Assuming a plain enumerator needs the enum name in front of it
What it looks likeint col = Color::green;Why it failsThis is the qualified style of the scoped form. A plain enumerator is entered directly into the enclosing scope, so the bare name green is already visible.
Do this insteadWrite int col = green; when the enum is plain.
Common mistake Counting the integer values from 1 instead of 0
What it looks likePredicting that green is 2 in enum Color { red, green, blue };
Why it failsBy default, integer values start at 0, so the first enumerator is 0 and green is 1.
Do this insteadCount from zero in list order: red is 0, green is 1, blue is 2.
Common mistake Expecting a cast to be required for the conversion
What it looks likeBelieving that int col = green; is an error and needs (int)green
Why it failsA plain enumerator implicitly converts to its integer value, so no cast is written.
Do this insteadRead the assignment as storing the enumerator's integer value.
Common mistake Reusing an enumerator name in the same scope
What it looks likeTwo plain enums in one scope both declaring green
Why it failsPlain enumerator names share the surrounding scope, so the two declarations clash.
Do this insteadUse distinct enumerator names across the scope, or place the enums in separate scopes.
Practicing the Count
Given enum Shape { circle, square, triangle };, what integer value does the assignment int x = triangle; store in x? Work it out before checking the hints.
Hints
- Identify the position of triangle in the list.
- The first enumerator, circle, has value 0.
- Each following enumerator adds one: square is 1 and triangle is 2.
Key Takeaways
- A plain enum is an enumeration whose enumerators are entered into the same scope as the enum name, and which implicitly converts to integers.
- The plain form is obtained by removing the class from enum class, which trades explicit qualification and conversion for convenience.
- Plain enumerator names are visible in the enclosing scope without qualification, so they share that scope with every other name declared there.
- By default, integer values start at 0 and increase by one for each enumerator in list order, and an assignment to an int stores that value without a cast.