Search

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) is a data type where every possible value is defined as a symbolic constant (called an enumerator). Enumerations are declared via the enum keyword. Let’s look at an example:

Declaring an enumeration 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 enums

Enum identifiers are often named starting with a capital letter, and the enumerators are often named using all caps. 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:

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 cannot forward declare enum types. However, there is an easy workaround. 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) Declare 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) given the value of prior enumerators (eg. COLOR_MAGENTA = COLOR_RED)

Quiz answers

1) Show Solution

2) Show Solution

3) Show Solution

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

147 comments to 4.5 — Enumerated types

  • Hamed O.Khaled

    Hi Alex!
    I’m trying to develop a simple chess engine right now , But I confused about the necessity of using enumeration data types to represent pieces of chess can’t we replace it with string piece [] = "WHITE_PAWN", … it will suffice the symbolic goal too ..

    • nascardriver

      Hi Hamed!
      Strings use more memory, are slower, and you could accidentally modify them at runtime. Also, when you want to check if a piece is eg. a bishop you might misspell a string causing your program to fail.

      Enums are the way to go.

  • Alok

    For Printing Strings Better way could be..
    enum Color
    {
        COLOR_BLACK, // assigned 0
        COLOR_RED, // assigned 1
        COLOR_BLUE, // assigned 2
        COLOR_GREEN, // assigned 3
        COLOR_WHITE, // assigned 4
        COLOR_CYAN, // assigned 5
        COLOR_YELLOW, // assigned 6
        COLOR_MAGENTA, // assigned 7
        COLOR_MAX   ///assiged 8

    };

    strings sColor[COLOR_MAX]
    {
        "COLOR_BLACK", // assigned 0
        "COLOR_RED", // assigned 1
        "COLOR_BLUE", // assigned 2
        "COLOR_GREEN", // assigned 3
        "COLOR_WHITE", // assigned 4
        "COLOR_CYAN", // assigned 5
        "COLOR_YELLOW", // assigned 6
        "COLOR_MAGENTA" // assigned 7
    };

    void printColor(Color color)
    {
           if(color < COLOR_MAX)
             std::cout << sColor[color];
           else
            std::cout << "Who knows!";
    }

  • Hema

    In line 5 and 12, why didn’t the enumerators end with a semi-colon?

    • nascardriver

      Hi Hema!
      The last entry of an enum doesn’t need a comma, it’s optional.
      Using a comma after the last element makes it easier to add more entries, there’s no semantic difference though.

  • Hello Alex and Happy New Year!

    According to the C++ Core Guidelines, it seems the use of ALL_CAPS (á la macros} in enums and enum classes is frowned upon. From my own perspective, I find it more useful, handy and readable. What’s your take?

    • Alex

      Personally, I like using all caps for enumerators (and macros), but that’s likely because it’s what I’m used to. Google suggests that naming enumerators like constants is better since they are essentially constants, and this helps avoid naming collisions with macros.

      I think they have a valid point, but I probably won’t change. 🙂

  • Benjamin

    Hi Alex,

    thanks for the great tutorials!

    I am playing around with sorting algorithms inspired by the later lesson on selection sort. I am aiming at writing a header file including a general "sortArray"-function, to which the user can pass an argument for picking the algorithm he wishes to apply. For this argument i defined an enum class called "Algorithm". Now the forward declaration of my "sortArray"-function inside the header file needs that enum type as well as the header’s cpp-file hosting the actual function.

    Is it good practice to make a header file just for the enum definition and include it to the "main-header" as well as its underlying cpp? It feels a bit wrong to me to include a header into a header. Is there any better way?

    • Alex

      It’s fine to include a header in a header if the including header needs the definitions in the included header.

      However, in this case, if your Algorithm enum is only used in the header containing the function forward definitions that use the Algorithm type, then I’d probably just define Algorithm in that same header (you can always split it out later if needed).

      • Benjamin Kambs

        Thanks for your answer and Merry Christmas first of all (even though it is basically over here by now).

        I did not idle myself and kept digging a bit more about the topic. I read you actually stated that it is a common thing to include headers into headers (I guess in the lesson about header guards). Sorry I did not cross check enough. However, another issue in that context puzzled me a bit. I found the answer to this one as well, but I like to hear from a programming veteran, whether my interpretation on why c++ was constructed like that is correct:

        The way I designed my program, the enum definition was included into my main.cpp (via the header in the header) and also into my second cpp (via the enum definition header directly). I did not pay attention first, but later remembered that having a function defined in multiple files would cause a linker error. So why not with enum definitions? Reading the wiki article about the one-definition-rule I saw that type definitions are only needed to be unique within a single translation unit. Just one possibility came to my mind, which would justify this different treatment: in some cases the compiler might need to know details about types. For example if a cpp file uses certain enumerations, it is clearly not enough to just declare the corresponding enum without defining it.  On the contrary the compiler does not care about what a function is doing as long as the functions’s parameters and return types fit - which is guaranteed by the forward declaration. Bottom line: in contrast to functions, multiple definitions can’t be entirely avoided for types. Therefore, the one-definition-rule is less strict on types. Is that about right?

        • Alex

          Yes, that’s right. With variables and functions, the compiler only needs to know about the variable or function prototype to do syntax checking. However, with types, the full type definition needs to be known. This necessitates including the full type definition into the places where it is used. Therefore, the one definition rule is relaxed for types.

  • fhmadcode

    Something about the Quiz 3f
    I have tested it in MSVC 2017, however, it is not a legal assignment.
    And the complier gave an error that "lvalue should be a modifiable value".
    So I think 3f is false?
    Do I make any mistake or misunderstand about this?
    My code is here:

    • Alex

      The wording on the question was poor (and has been updated). When the enumeration is being defined, you can initialize the enumerator with any valid integer value (or value that can be converted to an integer, which includes other enumerators). However, once defined, those enumerator values can’t be remapped, which is why your statement doesn’t work.

  • ZhiLier

    Hi Alex, I don’t know why "enumerated type declaration doesn’t take any memory", can you explain something about it ?

    • Alex

      Sure. When you’re defining a user-defined type, you’re just giving your type a name, and telling the compiler what it looks like. If you never use the type, no memory is needed. It’s only when you declare a variable of that type that memory is allocated.

      As an analogy, if I say, “An Apple is a fruit and can come in red, green, and yellow”, that tells you a bit about an apple (I’ve defined what an apple is). But how many apples do I have? None at this point, Apple is just a definition. I don’t need to reserve space in my fruit bowl until I acquire some actual apples.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter