Walletfox.com

Why you should use scoped enumerations

C++11 introduced scoped enumerations - enum class. At first sight, this might seem to you like a no-brainer. Yet, there are situations where scoped enumerations provide real benefits.

Consider the following example taken over from a drawing program. Our program needs to represent a variety of brush patterns and line styles. But, as you can see, both Brush Pattern and Pen Style have a type Solid.

Unscoped enumerations export their enumerators into the surrounding scope. As a result, if we omit the keyword class, we are going to get a name clash. This manifests itself as a compiler error: " 'Solid' conflicts with a previous declaration."

// Unscoped enum won't compile - name clash because of Solid
enum BrushPattern {Solid, Horizontal, Vertical, Cross};
enum PenStyle {Solid, Dash, Dot, DashDot};

In C++03, we could have provided scope for the enumerations by placing them into dedicated classes, i.e. Pen, Brush. See below. Unfortunately, this approach is not always applicable. We do not always have access to the Brush / Pen API. Also, at times we want to make the enumerations available to unrelated classes.

// C++03 Providing scope for enumerations by placing them into a class
class Brush {
    public:
        enum BrushPattern {Solid, Horizontal, Vertical, Cross};
};

class Pen {
    public:
        enum PenStyle {Solid, Dash, Dot, DashDot};
};

void f(Pen::PenStyle p){
    if(p == Pen::Solid){
    }
}

The second course of action in C++03 involves a name change. For example, we could have replaced the value Solid by SolidLine. This is inelegant and rather tedious. Judge for yourself:

// Unscoped enum - changing the name, compiles, but tedious
enum BrushPattern {SolidPattern, HorizontalPattern, VerticalPattern, CrossPattern};
enum PenStyle {SolidLine, DashLine, DotLine, DashDotLine};

This is where scoped enumerations of C++11, i.e. enum class come to the rescue. We place the keyword class after the enum specifier. In this way, we get the desired effect without any workarounds.

// C++11 Scoped enum - compiles
enum class BrushPattern {Solid, Horizontal, Vertical, Cross};
enum class PenStyle {Solid, Dash, Dot, DashDot};

Explore I:

1. Assume that you want to represent the following groups of fonts. Given that you use old-style unscoped enumerations, will the following code compile?

enum Serif {Courier, Times, Garamond};
enum Monospace {Consolas, Courier, FixedSys};

2. Look at the snippet below. Given the scoped enumeration that represents the direction and a function that takes the enumeration as an argument, will the following code compile? The code in question is highlighted.

enum class Direction {
    North,
    West,
    South,
    East
};

void f(Direction d){
   if(d == North){

   }
}

An important property of scoped enumerations is that their values are strongly typed. This means that they aren't implicitly convertible to integers or any other type. This prevents erroneous uses such as the one below. The first function takes two integers, the second one takes an integer and an enumeration. Unfortunately, the functions have similar names. The developer made a mistake and passed the enumeration to the wrong function. This wouldn't have happened with enum class. Such a code wouldn't compile!

void f1(int a, int b){
    std::cout << a << ' ' << b;
}

void f2(FileType f, int b){
    std::cout << f << ' ' << b;
}

int main() {
    // the developer intended to call f2 but called f1 instead
    // unfortunately, with unscoped enums this compiles fine
    f1(Pdf, 2);
}

Explore II:

1. What is the underlying type of enum class? Is this different from unscoped enum?

2. The following code compiles and produces the output "But it comes here instead". Why are we getting this strange result despite the fact that we passed (Solid, true) as penStyle and penStroke respectively?

enum PenStyle {Solid, Dashed, DashDot};

void f(PenStyle penStyle, bool penStroke){
    if(penStyle){
        if(penStroke == Solid)
            std::cout << "This is what we expect";
        else
            std::cout << "Something else";
    }
    else
        std::cout << "But it comes here instead";
}

int main(){
    f(Solid, true);
}

Such a code wouldn't compile with enum class. Correct the code to work with scoped enums.

Reading recommendation:

Topher Winward published an interesting article about how omitting a '>' character caused an implicit conversion mayhem that wouldn't have happened with scoped enumerations. Read more about it here.

Summary card: TIP 1

  PDF

Tagged: C++