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, old functions sometimes 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 time

Question #1


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

Show Solution

Question #2


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

Show Solution

Question #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)

Show Solution


S.4.5a -- Enum classes
Index
S.4.4c -- Using a language reference

273 comments to S.4.5 — Enumerated types

  • sami

    Hi,
    I have some questions:

    1) TYPO: 'e' is missing from 'numerator':

    'If there is no previous enumerator, the numerator will assume value 0.'

    2) Would you please give an example? I got compile error by doing that!
    "Since enumerators evaluate to integers, and integers can be assigned to enumerators, enumerators can be initialized with other enumerators (though there is typically little reason to do so!"

    3) Got error on enum forward declarations! I got "COLOR_WHITE: undeclared identifier" in the following code. I don't know why?

    • nascardriver

      1) Thanks

      2)

      3)
      You can't use enumerators before the enum is defined. The forward declaration only allows you to declare enum variables (eg. as parameters in a forward declaration of a function).

  • Nahin

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

    Would you please elaborate more on it. I didn't get why we should avoid having two enumerators with the same value?

  • AE35_Unit

    Answers to #1, 2, 3 and my questions on enums. Questions are commented in the code. Please let me know if anyone needs clarification.  Thanks all.

    • nascardriver

      You will mostly prefix enumerators with their enum's name (After the next lesson). If you do that, `auto` can be used without compromising information.

      Before you cast something to an enum, you should verify that the value has a corresponding enumerator.

      > are LOCHNESS and NESSIE part of the "enum Monster" now?
      No, they're variables with the type `Monster`. The should follow the same naming convention as all your variables do.

  • Innervate

    For my answer to Q2 of the quiz I had the following:

    I based my answer off how in the last section ItemType Torch is defined:

    The solution shows that this was not the preferred method. I then changed my code to the following:

    Is this correct? shouldn't the example of ItemType Torch also be changed to reflect the preferred method eg.

    • nascardriver

      I updated the lesson to use list initialization, thanks for pointing out the inconsistency!
      The enum name prefix isn't necessary for `enum` types, use it as you wish. The next lesson introduces `enum class`, where the prefix is mandatory.

  • Van

    Output

    Enter: 9
    You entered 9

    Alex wrote "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"

    Does it mean 9 is assigned to an enumerator?  If yes, what is that enumerator?

    • nascardriver

      Variables of `enum` type can hold any value that can be stored by their underlying type (Usually `int`). This makes the cast legal, even if the input is 9, but there is no enumerator for 9. You can compare every of your enumerators to `color`, but none will match.
      If you want to make sure that the user entered a valid value, you can add another enumerator to the end of `Color`, eg. `COLOR_MAX`, and check if `inputColor >=0 && inputColor < Color::COLOR_MAX` before doing the cast. This only works if your enumerators are contiguous, ie. there are no gaps between enumerators because you manually assigned values.

  • Eric

    Hi,
    This probably slipped by me earlier but in your solution to quiz 2:

    Why initialise monsterType that way instead of { MONSTER_TROLL };  // which worked in the codeblocks compiler?
    Thanks!

    • nascardriver

      If there's another `enum` with a `MONSTER_TROLL` enumerator, there will be a name conflict. This isn't applied consistently in this lesson. The next lesson will show a good alternative to `enum` that enforces the prefix.

  • cgsousa

    Is there a possibility to extract automatic the names from the values using RTTI (Runtime Type-Information)?

  • Raffaello

    is there a difference between the three?

    also, is this a valid exception to the "Don’t assign specific values to your enumerators" best practice?

    • nascardriver

      > is there a difference between the three?
      See my comment here.

      > is this a valid exception
      It depends on how the enumerators are used. If they're printed out, it can be useful to have specific values so that you can easily trace back the number to an enumerator. In this case, `ParseResult` is only used internally by our code, so there's no need for specific values. I updated the lesson, thanks for pointing it out!

  • bobby smith

    is there a reason you didn't use brace initialization in the answer to question 2?

  • Thomas Kennings

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

    Can we get these in green boxes? I always feel good when I see the green boxes coming up because they tidy up my questions.

    • nascardriver

      Lesson updated. I only update the style when it gets pointed out or I edit the lesson for other reasons. If you see any more lessons, feel free to point them out and I'll get you nice green boxes :-)

  • antiriad7

    Hi,
    I wonder, can I define an enum, struct or another function inside a function? I think I missed something about possible places of definitions in this tutorial.

  • SappyB

    Is it considered "Best Practice" to use switch statements with enumerations? Or is if-else preferred? I'm guessing that switch statements would be "Best Practice", because enumerations are integers-in-order kinda like cases in switch statements. Also, would using switch statements in this case be more performant than if-else?

    • nascardriver

      Switches are preferred for limited sets of values (chars, enums). They're easier to read (Because you know there can't be comparisons other than equality) and the compiler can optimize them better.

  • AbraxasKnister

    There's an occurrence of non colorized best practice and rule.

    • nascardriver

      Thanks for pointing it out! The lessons are gradually updated to the new style, but it takes some time.

      • AbraxasKnister

        I'm working myself through the this lecture series slowly but constantly and might make similar remarks in the future (I already encountered the same in the lectures about pointers and arrays). I figure you might find it helpful so just leave it uncommented and ignore it if you already know.

  • Armitage

    In forward declaration yes, but do we need specify base in definition? I have no error if I don't.

    • nascardriver

      Yes, every redeclaration has to have the same base specifier as the previous declarations. If your compiler doesn't error out, you might have forgotten to disable compiler extensions (Lesson 0.10).

      • Armitage

        You are right. But I have another problem:

        'COLOR_BLACK': undeclared identifier. So How i must use enum forward declaration correctly?

        • nascardriver

          You can't use an enumerator before it's defined. A forward declaration of an enum can be used to forward declare a function.

          In a small single-file project, this doesn't make sense. You might need it when you separate your code into multiple files to prevent circular dependencies.

          • Armitage

            I think I get It. Thanks.
            But VS2019 let you omit base type on enum declaration if it int.

            • nascardriver

              Yep, I double checked and VS doesn't error out even with /Za and /permissive-. You'll have to pay attention yourself not the omit the base when you redeclare the enum. This is non-standard behavior, if you rely on this, your code won't compile with other compilers.

  • kavin

    Hi, i have written a program based on 1st example. My doubt is, if its ok like the user below "Ri" has written the program without using MonsterClass enumeration parameter in the getRace() by directly accepting values as int? Without using a static_cast how will his program convert int input to compare with enumerators in MonsterRaces ?
    Here's my code. Is this ok or any improvements needed ?

    • nascardriver

      enumerators of an `enum` can be compared to an `int` without a cast, though, casting the `int` first is better to avoid mixed types.

      - Line 15-19: Those are magic numbers/strings. If you update `MonsterRaces`, this list is incorrect. Use your enumerators to print the selection.

      • kavin

        If i use without a cast i get this error:
        (42,40): error C2440: 'initializing': cannot convert from 'initializer list' to 'MonsterRaces'
        (42,40): message : Reason: cannot convert from 'int' to 'MonsterRaces'

        So i guess static_cast is compulsory in my case. I am using VS2019.

        For the line 15-19 i am not sure how to do that :( Even if i do a function with something like,
        if(MONRACE_ORCS==0)
        std::cout<<"Orcs-0\n";
        if(MONRACE_GLOBLINS==1)
        std::cout<<"Globlins-1"
        etc.,
        it would not work properly if i change the order or add a new race in enum MonsterRaces right? What is the code to do that?

        • nascardriver

          > If i use without a cast i get this error
          You can't initialize an `enum` with an `int`, you can only compare it to an `int`.

          Your solution would skip the monster if the enumerator's value is not the same as the value in the string. You still have a magic number/string. You can print the enumerators, rather than writing a value yourself.

      • kavin

        Sorry for double reply. I changed 15-19 like this but it wont print "Orcs-0". But it would out put as orcs if i input 0. I dunno why it is not getting printed ! Is this the correct way or ?

  • Charan

    Hey,It is stated that the enumerator can be input through keyboard. Let's say the enumerators are assigned(by us or by the system) like COLOR_BLUE = 3,COLOR_RED = 5. What if I entered a value of 7 in some integer variable x, and then static cast x into the type Color?

    • nascardriver

      All values that are valid for an enum's underlying type (eg. int), are valid for the enum. If you let the user input a value, or create an enumerator via a cast in general, you should verify that the value is one of your enumerators.
      If you manually assigned values to your enumerators, you'll have to check one after the other. If they were numbered automatically, you can check if the untrusted value is a value is in the range from your lowest enumerator to your highest enumerator.

  • 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]