Concepts / Implicit Conversions in C++

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.

  • Programming

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.

cpp

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?

  • 0

  • 1

  • 2

  • The program does not compile

Reveal answer
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

Definition

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.

Names in enclosingscopeno qualifier neededQualified namesenum name neededImplicit intconversionenumerator converts to intNo implicit intconversionexplicit conversionrequired
How do the scope, name visibility, and implicit integer conversion differ between a plain enum and an enum class?

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.

Enclosing scopewhere Color is declaredColorenum nameredenumeratorgreenenumeratorblueenumeratorcontainscontainscontainscontains
Which names are visible outside the enum, and what does the enclosing scope contain once a plain enum is declared?

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.

red0green1blue2plus 1plus 1
If an enum lists several names, what integer does each name become when it is assigned to an int, and how does each value follow from the one before it?
Worked example

Counting the values for the Color enum

The Color enum lists red, green, blue in that order. What integer does each enumerator convert to?

  1. 1
    Start the count

    The first enumerator in the list, red, has the value 0.

  2. 2
    Count to the next name

    green is the second enumerator, one greater than the value before it, so its value is 1.

  3. 3
    Count to the last name

    blue is the third enumerator, one greater than green, so its value is 2.

  4. 4
    Check the assignment

    int col = green; therefore stores 1 in col, because the enumerator implicitly converts to its integer value.

Result

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 like

    Color c = Color::green; when Color is a plain enum

    Why it fails

    A plain enum places its enumerators in the enclosing scope, so the name green is reachable directly and no extra qualifier is needed.

    Do this instead

    Write green directly in the code that uses it.

  • Common mistake

    Assuming enumerator values start at 1

    Why it fails

    By default the values start at 0 and increase by one for each additional enumerator.

    Do this instead

    Count from 0 in list order, so the first enumerator is 0.

  • Common mistake

    Believing an enumerator name clashes only with other enums

    Why it fails

    The 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 instead

    Check 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 fails

    A plain enumerator implicitly converts to its integer value, so no cast is written.

    Do this instead

    Read 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.

Definition

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.

Source lineCompilerVector classVector v1(7);direct constructor call with 7Vector v2 = 7;look for int to Vector conversionnone exists: compile error
In a declaration like Vector v2 = 7; versus Vector v1(7);, what steps does the compiler take in each case, and where does the conversion path differ?

Finding the Line Where a Conversion Fails

cpp
Notes on the code
  • 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.
Debugging

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.

EASY

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

MEDIUM

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.

More concepts starting with I