1. This site uses cookies. By continuing to use this site, you are agreeing to our use of cookies. Learn More.

C++ 11 Better Type System: Scoped Enums

Discussion in 'C++' started by BiplabKamal, Feb 24, 2016.

  1. C is not a strong type based language but C++ has been trying to become a stronger type based language. Classes and structures are example of strong types whereas the implicit conversion between different types is an example of weak type system. For example, in the following decelerations implicit conversions are example of weak type systems.
    Code:
        char c= 20;; // Implicit casting from int to char
        short s =c; //Implicit casting from char to short
        int b = true;//Implicit casting from bool to int
        bool bl = 10;//Implicit casting from int to bool
    
    Pointers are another tool to break strong typing. Pr-processor macros also not strong typed. C++11 introduced more type safety in case enumerator type.

    Scoped Enums



    Let us first look into the loopholes in the traditional C++98 style enumerators-

    As per normal rule of declaration when you declare a name inside curly braces the visibility of the name is limited to the scope defined by the curly braces. Like the visibility of a class member is limited within the scope of the class. This means if you want to use the members you have to use the class name(or instance of the class) also. This is not the case for C++98 enumerators. Visibility of enumerator item names are not limited within the enumerator scope but leaks to the scope containing the enumerator. This means if you have an enum like:
    Code:
    enum ElementaryColor
    {
        RED,
        GREEN,
        BLUE
    };
    
    you can uses the type like:

    auto color = RED;

    This also means you cannot use the same names for other variables or functions in the scope containing the enumerator.

    Following code snippet of an enumerator definition will give compile errors:
    Code:
    enum ElementaryColor
    {
        RED,
        GREEN,
        BLUE
    };
    enum  RainbowColor 
    {
        VIOLET = 1,
        INDIGO,
        BLUE,
        GREEN,
        YELLOW,
        ORANGE,
        RED
    };
    
    The above enumerators will give compile errors because the names RED, GREEN and BLUE are conflicting. This problem can be more serious if you use two independent 3rd party libraries which define enumerators with conflicting names.

    Another issue is the sizes of enumerators. In C++98 style enumerators behave like just constant integer. This is because the underlying type is integer. You cannot not change the underlying type to make it more memory efficient.

    Also C++98 enum converts to int implicitly. Following code will compile under C++98 compiler.
    Code:
    enum  RainbowColor 
    {
        VIOLET = 1,
        INDIGO,
        BLUE,
        GREEN,
        YELLOW,
        ORANGE,
        RED
    };
    void ShowColor(RainbowColor)
    {
        //Code here
    }
    
    int main()
    {
        ShowColor(10);// Passing an invalid value
        return 0;
    }
    
    If you see the line of code calling the 'ShowColor()' function, compiler has no clue that an invalid value is passed.

    So how the C++11/C++14 addresses the issues with enums discussed above?

    C++11 introduced a new kind of enumerator type called scoped enum. We will call the traditional enumerators as unscoped enums. Scoped enums are like classes or structures with static values. They are not visible outside the enumerator and need to be qualified with enumerator name. They are declared using 'class' or 'struct' keywords shown later. Their default underlying type is int but can be changed to other integral types to manage the sizes of enums memory efficiently. They cannot be type casted implicitly but explicitly.

    C++11 also upgraded the existing unscopped enums to be qualified with enum name(but optional), specifying underlying type other than integer and restriction to implicit type conversion. Below is the syntax of unscoped and scoped enums in C++11:
    Code:
    // unscoped enum:
    enum [identifier] [: type]
    {enum-list};*
    
    // scoped enum:
    enum [class|struct] 
    [identifier] [: type] 
    {enum-list}
    
    Where identifier is the enumerator name and type is the underlying integral type.

    The major advantage of scoped enum is reduction in namespace pollution. Scoped enum will solve the name conflict problem and allow to have same name in the enum list of two different enums in the same namespace. Following code in C++11 declaring a scoped enum and an unscoped enum with common names:
    Code:
    #include <cstdint>
    //unscoped enum
    enum ElementaryColor :int8_t
    {
        RED,
        GREEN,
        BLUE
    };
    
    //scoped enum
    enum   class RainbowColor :int8_t
    {
        VIOLET,
        INDIGO,
        BLUE,
        GREEN,
        YELLOW,
        ORANGE,
        RED
    };
    
    int main()
    {
        int i = RED;// unscoped enum does not need explicit type casting. Captures ElementaryColor::RED
    
        //int k = YELLOW; // Error: Scoping and explicit casting is required
        int j = (int)RainbowColor::YELLOW; // Scoped and explicit casting is used
    
        ElementaryColor c = RED;// unscoped enum 
        if (c < 5.5) // Comparison with an invalid value is allowed
            c = BLUE;
        RainbowColor rc = RainbowColor::INDIGO;//Scoped enum
        if (rc > 10)//Comparision is not allowed
            rc = RainbowColor::YELLOW;
    }
    
    In C++11 you can control the size of enums by specify underlying type. Default type is int but can be set to char, unsigned long etc. It also introduced new types to be used for enums which are defined in <cstdin> header and the new types are std::int8_t, std::int16_t, std::int32_t, and std::int64_t and unsigned versions of those begin with u like std::uint8_t.

    These types are of known sizes across compilers and architectures. For example you can declare an enum like:
    Code:
    #include <cstdint>
    enum class Colors : std::int8_t { RED, GREEN, BLUE };
    
    Another new feature of scoped enum is forward deceleration. Forward declaration helps to cut down re-compilation time when enum definitions get changed in some scenarios. Consider an example where you have an enum with a big enumerator-list and the list is generated by some third party software and the list is going to be changed very frequently. So in normal scenario your code referring to the enum needs to be compiled whenever the enumerator-list get changed. This is a cumbersome situation and programmers hate this kind of scenario.

    C++11 allows you to forward declare an enum with known underlying data type. So if your code is referring to the enum but not referring the enumerator list then your code is not required to re-compile whenever the enum definition (enumerator list) is changed. Forward declaration of enums are shown below:
    Code:
    enum   class RainbowColor;//Scoped Enum with default underlying type of integer
    enum   class Countries :short;//Scoped Enum with specified underlying type of short integer
    enum   Colors:int;// Unscoped Enum with underlying type of integer
    
    To get an idea of the usage of forward declaration of enum type let us consider an example where a drawing application code uses a scoped enum for the list of colors it supported. Suppose a header file named colors.h contains the enum definition like:
    Code:
    enum   class Colors
    {
        BLACK,
        RED,
        GREEN,
        BLUE,
        //Hundreds of colors listed here
        ...
        WHITE
    };
    
    You also have functions which takes the user choice of color and apply the color. Suppose the two function's prototype are shown below:
    Code:
        Color GetUserColor(); // Ger the color chosen by user
    
        void ApplyColor(Color cl); //Apply the color in the user context
    
    Now without forward declaration you have to include colors.h wherever you use above functions. In this case every time the enumerator list in the Color enum is changed(which is much more possibility) you need to compile your code. With forward declaration of enum you can avoid this re-compilation. The code will look like:
    Code:
    enum   class Color;
    Color GetUserColor();
    
    void ApplyColor(Color cl);
    
    void main()
    {
        Color mycolor = GetUserColor();
        ApplyColor(mycolor);
    }
    
    To compile the above code compiler does not need to know the definition of the enum but size of the enum should be known. Size of the enum depends on the underlying type of the enum. For scoped enum the default underlying type is integer and for unscoped enum it has to be specified explicitly. Above enum could have been declared as
    Code:
    enum   class Color:int;
        or
    enum   Color:int;
    
    I have been doing C++ programming since year 1999 and it was frustrating to use unscoped enumerator type because of the visibility leak. Thanks to modern C++ to add the strong type based enumerator.
     

Share This Page