8.3 — Unscoped enumeration input and output

You might be surprised to find out that enumerators are actually just named integral constants (and as a result, enumerated types just hold an integral value).

This is similar to the case with chars (%Failed lesson reference, id XX%). Consider:

A char is really just a small integer, and the character 'A' gets converted to an integral value (in this case, 65) and stored.

When we define an enumerator, each enumerator is automatically assigned an integer value based on its position in the enumerator list. By default, the first enumerator is assigned the integer value 0, and each subsequent enumerator has a value one greater than the previous enumerator:

It is possible to explicitly define the value of enumerator. These integer values can be positive or negative and can share the same value as other enumerators. Any non-defined enumerators are given a value one greater than the previous enumerator.

Note in this case, horse and giraffe have been given the same value. When this happens, the enumerators become non-distinct -- essentially, horse and giraffe are interchangeable. Although C++ allows it, assigning the same value to two enumerators in the same enumeration should generally be avoided.

Best practice

Avoid assigning explicit values to your enumerators unless you have a compelling reason to do so.

Unscoped enumeration evaluation and output

Consider the following program:

This prints:


When an enumerated value is used in a function call or with an operator, the compiler will first try to find a function or operator that matches the enumerated type. For example, when the compiler tries to compile std::cout << paint, the compiler will first look to see if std::cout knows how to print an object of type Color (because paint is of type Color). It doesn't.

If the compiler can't find a match, the compiler will then implicitly convert an unscoped enumerator to an integer value. Because std::cout does know how to print an integral value, the value in paint gets converted to an integer and printed as integer value 1.

Switches and enumerations

DISCUSS HERE and update examples

Printing enumerator names

As you saw in the example above, trying to print an enumerated value using std::cout results in the integer value of the enumerator being printed.

If you want to print an enumerator as text, you'll need to write a function to do that yourself. The most primitive way to do this is to write a print function that uses a series of if-statements:

This prints:


For advanced readers

Once you've learned to use switch statements (%Failed lesson reference, no id%), you'll probably want to use those instead of a bunch of if/else statements, as it's likely to be more performant and more readable.

The above method has one major downside: because printColor returns void, printColor can't be called on the same line that std::cout is used. As a result, printing a single line of text gets broken into 3 statements!

There are two good solutions for fixing this, both of which use concepts we haven't talked about yet. First, instead of having your function print the enumerator, you can have it return the enumerator name as a std::string_view or std::string:

You don't need to know how std::string_view or std::string work here (we'll explain them later in this chapter) -- just know they help return the string literal back to the caller, and that std::cout knows how to print such text values.

An alternative method is to teach std::cout how to print the value of your program-defined enumerated type:

Unscoped enumerator input

While the compiler will implicitly convert unscoped enumerators to an integer, it will not implicitly convert an integer to an enumerated value. The following will produce a compiler error:

However, you can force the compiler to convert an integer to an unscoped enumerator using static_cast:

There's one particular case where this can be useful. The compiler won't let you input an enumerated value using std::cin:

To work around this, we can read in an integer, and use static_cast to convert the integer to an enumerator of the appropriate enumerated type:

Enumeration size and base

Enumerated types are considered part of the integer family of types, and it's up to the compiler to determine how much memory to allocate for an enum variable. The C++ standard says the enum size needs to be large enough to represent all of the enumerator values. Most often, it will make enum variables the same size as a standard int.

However, it is possible to specify a different underlying type. For example, if you are working in some bandwidth-sensitive context (e.g. sending data over a network) you may want to specify an smaller type:

Since enumerators aren't usually used for arithmetic or comparisons, it's generally safe to use an unsigned integer if desired.

Best practice

Specify the base type of an enumeration only when necessary.

Quiz time

Question #1

True or false. Enumerators can be:

* Given an integer value

Show Solution

* Not assigned a value

Show Solution

* Given a floating point value

Show Solution

* Given a negative value

Show Solution

* Given a non-unique value

Show Solution

* Initialized with the value of prior enumerators (e.g. COLOR_MAGENTA = COLOR_RED)

Show Solution

8.3 -- Scoped enumerations (enum classes)
8.2 -- Introduction to unscoped enumerations

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">