8.3 — Scoped enumerations (enum classes)

Although unscoped enumerations are distinct types in C++, they are not type safe, and in some cases will allow you to do things that don’t make sense. Consider the following case:

This prints:

color and fruit are equal

When color and fruit are compared, the compiler will look to see if it knows how to compare a Color and a Fruit. It does not. Next, it will try converting Color and/or Fruit to integers to see if it can find a match. Eventually it will determine that if it converts both to integers, it can do the comparison. Since color and fruit are both set to enumerators that convert to integer value 0, color will equal fruit.

This is doesn’t make sense semantically since color and fruit are from different enumerations and are not intended to be comparable. With standard enumerators, there’s no easy way to prevent this.

Because of the above challenge, as well as the namespace pollution problem (unscoped enumerations defined in the global scope put their enumerators in the global namespace), the C++ designers decided a better solution was needed.

Scoped enumerations

That solution is the scoped enumeration (often called an enum class for reasons that will become obvious shortly).

Scoped enumerations work similarly to unscoped enumerations (8.2 -- Introduction to unscoped enumerations), but have two primary differences: They are strongly typed (they won’t implicitly convert to integers) and scoped (they act like namespaces for their enumerators).

To make an scoped enumeration, we use the keyword class after the enum keyword. Here’s an example:

This program produces a compile error, since the compiler will disallow comparing a scoped enumerator with another type.

Scoped enumerations define their own scope regions

Unlike unscoped enumerations, which place their enumerators in the same scope as the enumeration itself, scoped enumerations place their enumerators only in their own scope region. In other words, scoped enumerations act like a namespace for their enumerators. This built-in namespacing helps reduce global namespace pollution and the potential for name conflicts when scoped enumerations are used in the global scope.

To access a scoped enumerator, we do so just as if it were in a namespace having the same name as the scoped enumeration:

Note that because the enumerators are inside the scope region of the scoped enumeration, there’s no need to prefix your enumerator names with the name of the enumeration (e.g. it’s okay to name your enumerator red instead of color_red, since Color::color_red is redundant).

Also note that because scoped enumerations offer their own implicit namespacing for enumerators, there’s no need to put scoped enumerations inside an explicit namespace (unless there’s some other compelling reason to do so), as it would be redundant.

Scoped enumerations don’t implicitly convert to integers

Scoped enumerators won’t implicitly convert to integers. In most cases, this is a good thing because it rarely makes sense to do so, and it helps prevents semantic errors, such as comparing enumerators from different enumerations, or trying to evaluate expressions like red + 5.

Note that you can still compare enumerators from within the same enum class (since they are of the same type):

There are occasionally cases where it is useful to be able to treat a scoped enumerator as an integer. In these cases, you can explicitly convert an enum class enumerator to an integer by using a static_cast to int:

Conversely, you can also static_cast an integer to a scoped enumerator, which can be useful when doing input from users:

Best practice

Favor scoped enumerations over unscoped enumerations unless there’s a compelling reason to do otherwise.

As an aside...

The class keyword, along with the static keyword, is one of the most overloaded keywords in the C++ language, and can have different meanings depending on context. Although enum classes use the class keyword, they aren’t considered to be a “class types”. We’ll cover actual class types later.

Quiz time

Question #1

Define an enum class named Animal that contains the following animals: pig, chicken, goat, cat, dog, ostrich. Write a function named getAnimalName() that takes an Animal parameter and uses a switch statement to return the name for that animal as a std::string. Write another function named printNumberOfLegs() that uses a switch statement to print the number of legs each animal walks on. Make sure both functions have a default case that prints an error message. Call printNumberOfLegs() from main() with a cat and a chicken. Your output should look like this:

A cat has 4 legs.
A chicken has 2 legs.

Show Solution

8.12 -- Struct definition and member selection
8.3 -- Unscoped enumeration input and output

Leave a Reply

You can use these HTML tags
