Implicit Conversions in C++
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 to use each choice as a plain name rather than a qualified one. C++ enumerations provide that tool. This article starts from a short program that uses one, and then works backward from what the program does to the rules that explain it.
Read these two lines once without running anything. Notice that green is written with no enum name in front of it, and that the result is stored in an ordinary int. Keep in mind what col ends up holding, because the next step asks you to commit to an answer before the explanation arrives.
What do you think happens?
After int col = green; runs, what integer value does col hold?
Reveal answer
1
The enumerators of Color start at 0 and increase by one in list order. red is 0, green is 1, and blue is 2. A plain enumerator implicitly converts to its integer value, so col receives 1 and no cast is written.
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 ordinary enum keyword, followed by a name and a braced list of enumerators. The source describes it through a simple change to a related form. If you do not want to explicitly qualify enumerator names, and you want enumerator values to behave as ints without an explicit conversion, you remove the class from enum class. What remains is a plain enum. The plain form therefore trades explicit qualification and explicit conversion for convenience.
The two defining properties of a plain enum can be read as a pair of choices. The first is whether enumerator names must be explicitly qualified. The second is whether enumerator values convert to integers without an explicit conversion. A plain enum chooses the looser setting on both. A scoped enum class chooses the stricter setting on both.
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.
This placement is what makes a plain enum convenient to read. A name like green can be written directly in the code that uses it. The same placement also means the enumerator names share the surrounding scope with everything else declared there. This is part of why the source calls plain enums less well behaved than their scoped counterparts.
How Integer Values Are Counted
The integer a plain enumerator converts to is determined by its position in the list. By default, the 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.
Counting the values for the Color enum
The Color enum lists red, green, blue in that order. What integer does each enumerator convert to?
- 1Start the count
The first enumerator in the list, red, has the value 0.
- 2Count to the next name
green is the second enumerator, one greater than the value before it, so its value is 1.
- 3Count to the last name
blue is the third enumerator, one greater than green, so its value is 2.
- 4Check the assignment
int col = green; therefore stores 1 in col, because the enumerator implicitly converts to its integer value.
red is 0, green is 1, blue is 2, and col holds 1 after int col = green;.
Once you can do this count reliably, you can predict the value of any enumerator in a short plain enum without running the code. The count depends only on list order, not on the spelling of the names.
Mistakes Beginners Make with Plain Enums
Common mistake Writing a qualifier in front of a plain enumerator
What it looks likeColor c = Color::green; when Color is a plain enum
Why it failsA plain enum places its enumerators in the enclosing scope, so the name green is reachable directly and no extra qualifier is needed.
Do this insteadWrite green directly in the code that uses it.
Common mistake Assuming enumerator values start at 1
Why it failsBy default the values start at 0 and increase by one for each additional enumerator.
Do this insteadCount from 0 in list order, so the first enumerator is 0.
Common mistake Believing an enumerator name clashes only with other enums
Why it failsThe enumerator names share the surrounding scope with everything else declared there, so a name already used in that scope can collide with an enumerator.
Do this insteadCheck the enclosing scope for names that match an enumerator before adding a new declaration.
Common mistake Expecting a cast before storing an enumerator in an int
Why it failsA plain enumerator implicitly converts to its integer value, so no cast is written.
Do this insteadRead int col = green; as storing 1 without any explicit conversion.
Older and existing C++ code often contains plain enums, because the source notes they have been part of C++ and C since the earliest days of both languages and remain common in current code. Recognizing their two properties will help you read that code even when you would choose a scoped form for new work.
The Int-to-Vector Misconception
Many learners reading C++ for the first time notice that a Vector can be created from a single integer. If Vector v1(7) produces a vector holding 7 elements, it seems natural to think that the number 7 is simply turned into a Vector wherever a Vector is expected. Under that belief, Vector v2 = 7; should work just as well. The source material makes a precise claim against that belief: there is no implicit conversion from int to Vector.
An implicit conversion is a conversion the compiler performs automatically, without any explicit cast written by the programmer.
The key distinction is between two operations. Creating a Vector from the number 7 by calling a constructor is a direct, explicit action written in the code. Letting the compiler silently replace a 7 with a Vector at an assignment is a different operation, and the source says that operation is not available for this class. Being able to construct an object directly does not mean the compiler will convert values of that type implicitly.
Finding the Line Where a Conversion Fails
- Line 1 calls a constructor directly with the value 7.
- Line 2 asks the compiler to turn the int 7 into a Vector at the assignment, which the source says is not allowed.
When a declaration does not behave as expected, ask where the actual result first departs from the expected one. The expected result of the second statement is a Vector holding 7 elements, because the first statement shows that a Vector can hold 7 elements. The actual result is a compile error at the assignment. The divergence therefore occurs at the assignment, not at the constructor call. Narrowing the problem to that one line is the fastest way to find the cause.
Given Vector v1(7); and Vector v2 = 7;, which line fails to compile, and what is the reason the compiler gives in terms of conversion?
Hints
- Ask whether the line constructs a Vector directly or asks the compiler to convert an int automatically.
- The source states there is no implicit conversion from int to Vector.
Checking Your Reading of Plain Enum Code
Using enum Color { red, green, blue };, predict the integer value stored in int c = blue; and explain how you counted it.
Hints
- Count from 0 in list order.
- Blue is the third name in the list.
Key Takeaways
- A plain enum enters its enumerators into the same scope as the enum name and implicitly converts each enumerator to an integer, without a cast.
- Enumerator values count from 0 by default and increase by one in list order, so red is 0, green is 1, and blue is 2 in the Color example.
- Removing class from enum class gives the plain form, which trades explicit qualification and explicit conversion for convenience.
- Being able to construct a Vector directly from an int, as in Vector v1(7);, does not mean an int converts implicitly to a Vector, so Vector v2 = 7; fails at the assignment.
- To debug a failing declaration, find the exact line where the actual result first departs from the expected one.