Search

S.4.5 — Enumerated types

C++ contains quite a few built in data types. But these types aren’t always sufficient for the kinds of things we want to do. So C++ contains capabilities that allow programmers to create their own data types. These data types are called user-defined data types.

Perhaps the simplest user-defined data type is the enumerated type. An enumerated type (also called an enumeration or enum) is a data type where every possible value is defined as a symbolic constant (called an enumerator). Enumerations are defined via the enum keyword. Let’s look at an example:

Defining an enumeration (or any user-defined data type) does not allocate any memory. When a variable of the enumerated type is defined (such as variable paint in the example above), memory is allocated for that variable at that time.

Note that each enumerator is separated by a comma, and the entire enumeration is ended with a semicolon.

Prior to C++11, a trailing comma after the last enumerator (e.g. after COLOR_MAGENTA) is not allowed (though many compilers accepted it anyway). However, starting with C++11, a trailing comma is allowed. Now that C++11 compilers are more prevalent, use of a trailing comma after the last element is generally considered acceptable.

Naming enumerations and enumerators

Providing a name for an enumeration is optional, but common. Enums without a name are sometimes called anonymous enums. Enumeration names are often named starting with a capital letter.

Enumerators must be given names, and are typically named either using all caps (e.g. COLOR_WHITE), or prefixed with a k and intercapped (e.g. kColorWhite).

Enumerator scope

Because enumerators are placed into the same namespace as the enumeration, an enumerator name can’t be used in multiple enumerations within the same namespace:

Consequently, it’s common to prefix enumerators with a standard prefix like ANIMAL_ or COLOR_, both to prevent naming conflicts and for code documentation purposes.

Enumerator values

Each enumerator is automatically assigned an integer value based on its position in the enumeration 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:

The cout statement above prints the value 4.

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, ANIMAL_HORSE and ANIMAL_GIRAFFE have been given the same value. When this happens, the enumerations become non-distinct -- essentially, ANIMAL_HORSE and ANIMAL_GIRAFFE are interchangeable. Although C++ allows it, assigning the same value to two enumerators in the same enumeration should generally be avoided.

Best practice: Don’t assign specific values to your enumerators.
Rule: Don’t assign the same value to two enumerators in the same enumeration unless there’s a very good reason.

Enum type evaluation and input/output

Because enumerated values evaluate to integers, they can be assigned to integer variables. This means they can also be output (as integers), since std::cout knows how to output integers.

This produces the result:

5

The compiler will not implicitly convert an integer to an enumerated value. The following will produce a compiler error:

However, you can force it to do so via a static_cast:

The compiler also will not let you input an enum using std::cin:

One workaround is to read in an integer, and use a static_cast to force the compiler to put an integer value into an enumerated type:

Each enumerated type is considered a distinct type. Consequently, trying to assign enumerators from one enum type to another enum type will cause a compile error:

If you want to use a different integer type for enumerators, for example to save bandwidth when networking an enumerator, you can specify it at the enum declaration.

Since enumerators aren’t usually used for arithmetic or comparisons, it’s safe to use an unsigned integer. We also need to specify the enum base when we want to forward declare an enum.

As with constant variables, enumerated types show up in the debugger, making them more useful than #defined values in this regard.

Printing enumerators

As you saw above, trying to print an enumerated value using std::cout results in the integer value of the enumerator being printed. So how can you print the enumerator itself as text? One way to do so is to write a function and use an if statement:

Once you’ve learned to use switch statements, you’ll probably want to use those instead of a bunch of if/else statements, as it’s a little more readable.

Enum allocation and forward declaration

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

Because the compiler needs to know how much memory to allocate for an enumeration, you can only forward declare them when you also specify a fixed base. Because defining an enumeration does not allocate any memory, if an enumeration is needed in multiple files, it is fine to define the enumeration in a header, and #include that header wherever needed.

What are enumerators useful for?

Enumerated types are incredibly useful for code documentation and readability purposes when you need to represent a specific, predefined set of states.

For example, functions often return integers to the caller to represent error codes when something went wrong inside the function. Typically, small negative numbers are used to represent different possible error codes. For example:

However, using magic numbers like this isn’t very descriptive. An alternative method would be through use of an enumerated type:

This is much easier to read and understand than using magic number return values. Furthermore, the caller can test the function’s return value against the appropriate enumerator, which is easier to understand than testing the return result for a specific integer value.

Enumerated types are best used when defining a set of related identifiers. For example, let’s say you were writing a game where the player can carry one item, but that item can be several different types. You could do this:

Or alternatively, if you were writing a function to sort a bunch of values:

Many languages use Enumerations to define booleans. A boolean is essentially just an enumeration with 2 enumerators: false and true! However, in C++, true and false are defined as keywords instead of enumerators.

Quiz

1) Define an enumerated type to choose between the following monster races: orcs, goblins, trolls, ogres, and skeletons.

2) Define a variable of the enumerated type you defined in question 1 and assign it the troll enumerator.

3) True or false. Enumerators can be:
3a) given an integer value
3b) not assigned a value
3c) given a floating point value
3d) negative
3e) non-unique
3f) initialized with the value of prior enumerators (e.g. COLOR_MAGENTA = COLOR_RED)

Quiz answers

1) Show Solution

2) Show Solution

3) Show Solution

S.4.5a -- Enum classes
Index
S.4.4b -- An introduction to std::string

234 comments to S.4.5 — Enumerated types

  • ErwanDL

    Hey Alex, why is it still written "Because the compiler needs to know how much memory to allocate for an enumeration, you cannot forward declare enum types.", although you wrote a few lines above that forward declaration of enums is possible (since C++11 if I'm right), as long as you also forward declare the enum base ?

    • nascardriver

      Hi!

      I missed that sentence when I added the paragraph about forward declarations. Base specifications and forward declarations are indeed possible since C++11. Thanks for pointing it out! Lesson updated.

  • Ri

    Love the tutorials so far! Good job making them very intuitive. I tried to create a sort of spawning system for game (with what little knowledge I have!), any tips or ways to improve this code would be greatly appreciated. Many thanks.

    • nascardriver

      Hi!

      - If your program prints anything, the last thing it prints should be a line feed ('\n').
      - Line 6 uses neither `monster` nor `monName`. If you update the string, `monName` or `monsters`, your program prints wrong instructions.
      - Line 26, 28, etc: You don't need to manually call `std::string`.
      - Avoid abbreviations.
      - `monName::monToSpawn` should be a `monster`.

    • Elis

      Hi! I fiddled a bit with this code to accept text input as opposed to integers.

      If nascardriver or anyone is around I'd be interested in hearing their opinion on the usage of the '?:' operator,
      too long and convoluted?

      • nascardriver

        > opinion on the usage of the '?:' operator
        The problem is that it's not reusable. Add `getMonsterByName` and `getNameOfMonster` with the conditionals inside, then it's ok.
        "ok", because in reality you would use different techniques that you don't know about yet. Your code shows that you understood the conditional operator, that will come in handy in other quizzes.

        > 32767
        Magic number. There's nothing stopping this value from being valid. Add a `MONSTER_INVALID` to `monsters` and return that instead. It's obvious in the code that this is an invalid value and it won't be a duplicate.

  • Omri

    "Consequently, it’s common to prefix enumerators with a standard ***prefix*** like ANIMAL_ or COLOR_, both to prevent naming conflicts and for code documentation purposes"
    Is it not a postfix in this example...

  • Mhz93

    In the last line of the first example of this lesson, the word "red" has been printed in black color which should be blue!!
    Also the same with the word "GREEN" in the second example.
    Also the word "POTION" in line 8 in the 15th example.
    Also the word "BACKWARDS" in line 4 in the 16th (the last) example.
    and finally "SKELETON" in 7th line in the solution of first queez.
    sorry I know that's not worth to mention!! but as I like this site and enjoy learning CPP from, I like to see this as well as possible.

  • Kris

    Hi nascardriver,

    No wonder there is so many comments on this topic. I found it very confusing myself, too.

    I have one comment: declaring ItemType itemType =  ITEMTYPE_TORCH is very confusing; why not to declare it this way: ItemType weapon = ITEMTYPE_TORCH. Just too many "itemtypes" for a novice. Couldn't make use of the function getItemName() to print out the name of the weapon.

    Although it forced me to read this part of the lesson many times, play with it in Visual Studio many times until I got it! :)

    Thanks.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]