Search

8.2 — Introduction to unscoped enumerations

// This lesson needs to be split in 2 or 3

C++ contains many useful fundamental data types (which we introduced in lesson 4.1 -- Introduction to fundamental data types). But these types aren’t always sufficient for the kinds of things we want to do.

For example, let’s say you’re writing a program that needs to keep track of whether an apple is red, yellow, or green, or what color a shirt is (from a preset list of colors). If only fundamental types were available, how might you do this?

You might store the color as an integer value, using some kind of implicit mapping (0 = red , 1 = blue, 2 = green):

But this isn’t at all intuitive, and we’ve already discussed why magic numbers are bad (4.13 -- Const, constexpr, and symbolic constants). We can get rid of the magic numbers by using symbolic constants:

While this is a bit better for reading, the programmer is still left to deduce that these variables (of type int) are meant to hold one of the values defined in the set of unrelated color symbolic constants (which are defined elsewhere).

We can make this program a little more clear by using a type alias:

We’re getting closer. Someone reading this code still has to understand that these color symbolic constants are meant to be used with variables of type color_t, but at least the type has a unique name now so someone searching for color_t would be able to find the set of associated symbolic constants.

Another problem is that nothing enforces using one of these color symbolic constants. We can still do something like this:

Also, if debug the any of these variables in our debugger, we’ll only see the integer value of the color (e.g. 0), not the symbolic meaning (red), which can make it harder to tell if our program is correct.

Fortunately, we can do better. Because selecting an item from a predefined set of related items (such as different colors) is so common, C++ provides a compound type to allow us to manage these sets of items more effectively.

Enumerations

An enumeration (also called an enumerated type or an enum) is program-defined data type where every possible value for the type is defined as an associated symbolic constant (called an enumerator).

C++ supports two kinds of enumerations: unscoped enumerations, which we’ll cover in this lesson, and scoped enumerations, which we’ll cover later in this chapter.

Unscoped enumerations

Unscoped enumerations are defined via the enum keyword. Let’s look at an example:

Remember, with program-defined types, we need to define what the type looks like before we can use it. So in the first part of this example, we define a a new unscoped enumeration named Color. Part of this definition includes the enumerators, which are the possible values that the enumeration can hold. In this example, our Color enumeration can have 3 possible values: red, blue, and green. Note that this definition only tells the compiler what the type looks like -- it does not cause any memory to be allocated.

Each enumerator must be separated by a comma (not a semicolon) -- a trailing comma after the last enumerator is optional but recommended for consistency.

The entire enumeration definition must end with a semicolon. Failure to include the semicolon that ends the type definition is a common programmer error, and one that can be hard to debug because the compiler will often error on the line after the type definition.

We then instantiate three objects of type Color, initializing each one with a different color value (memory is allocated for each these objects).

Naming enumerations and enumerators

By convention, the names of enumerated types start with a capital letter (as do all program-defined types). Providing a name for an enumeration is optional, but highly recommended. Enums without a name are sometimes called anonymous enums, but they are hardly ever used in practice.

New programmers sometimes find the following definition confusing because of the similarity between the type name and variable name:

But this is no different than any other variable definition: type comes first (Color), then variable name (color), then optional initializer (green). This naming convention helps ensure that our program-defined type names and our variable names don’t have a naming conflict.

The enumerators must be given names. Unfortunately, there is no common naming convention for enumerator names. Choices include starting with lower case (e.g. red), starting with caps (Red), all caps (RED), all caps with a prefix (COLOR_RED), or prefixed with a “k” and intercapped (kColorRed).

Modern C++ guidelines typically recommend avoiding the all caps naming conventions, as those are typically used for macros. We recommend also avoiding starting with a capital letter, as names beginning with a capital letter are typically reserved for program-defined types.

Best practice

Name your enumerated types starting with a capital letter. Name your enumerators starting with a lower case letter.

Author's note

You’ll see lessons in future chapters that still use the all-caps enumerator naming convention. We’re in the process of cleaning this up.

Enumerated types are distinct

Each enumerated type you create is considered to be a distinct type, meaning the compiler can distinguish it from other types (unlike typedefs or type aliases, which are considered non-distinct from the types they are aliasing).

Because enumerated types are distinct, enumerators defined as part of one enumerated type can’t be used with objects of another enumerated type:

What are enumerations 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 -- after all, 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.

The scope of unscoped enumerations

Unscoped enumerations were originally named such because they put their enumerator names into the same scope as the enumeration definition itself (as opposed to creating a new scope region like a namespace does).

For example, given this program:

The Color enumeration is defined in the global scope. Therefore, all the enumeration names (red, green, and blue) also go into the global scope. This pollutes the global scope and significantly raises the chance of naming collisions.

One consequence of this is that an enumerator name can’t be used in multiple enumerations within the same scope:

In the above example, both unscoped enumerations (Color and Feeling) try to put the name blue into the global scope. This leads to a naming collision and subsequent compile error.

Note: as of C++11, unscoped enumerations now also act as a named scope region for their enumerators (much like a namespace acts as a named scope region for the names declared within). This means we can access the enumerators of an unscoped enumerator as follows:

Most often, unscoped enumerators are accessed without using the scope resolution operator.

Avoiding enumerator naming collisions

There are quite a few common ways to prevent unscoped enumerator naming collisions. One option is to prefix each enumerator with the name of the enumeration itself:

This still pollutes the namespace but reduces the chance for naming collisions by making the names longer and more unique.

Another option is to put the enum inside a namespace (or something that defines its own named scope, like a class):

This means we now have to prefix our enumeration name and enumerator names with the name of the scoped region.

A related option is to use an scoped enumeration (which defines its own scope region). We’ll discuss scoped enumerations in the next lesson %Failed lesson reference, id XX%.

Best practice

Prefer to put your enumerations inside a named scope region so they don’t pollute the global namespace.

Quiz time

Question #1

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


8.3 -- Scoped enumerations (enum classes)
Index
8.1 -- Introduction to compound data types

62 comments to 8.2 — Introduction to unscoped enumerations

  • begginner

    #include<iostream>
    using namespace std;
    enum Monster{
        BOCKY,
        KING,
        FIREBALL,
        BULL,
        SCOOBY
        
    };
    function(Monster monster){
        switch(monster){
            case(BOCKY):
            cout<<"you choose a RAT!";
            break;
            case(KING):
            cout<<"you choose a LORD!";
            case(FIREBALL):
            cout<<"you choose a DRAGON!";
            break;
            case(BULL):
                cout<<"you choose a BULL!";
                break;
                case(SCOOBY):
                cout<<"you choose a DOG!";
        }
        
    }
    main(){
        
        cout<<function(FIREBALL);
    }
    why i am getting extra valur after my enum type.

  • Night Owl

    Hello Alex,

    On the quiz part of this section, what do you mean by " enumerators can be non-unique"?

    I know many people have said this, but thanks for all you hard work on the lessons and this website.

    • Alex

      I updated the wording to be more clear:

      "These integer values can be positive or negative and can share the same value as other enumerators"

      Basically, two different enumerators can resolve to the same value.

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