Scoped vs unscoped enum in C++

An enumeration is a distinct type (user-defined type) with named constants that are known as enumerators. The name of the enumeration becomes an enum name within its scope.

In C++, the enumeration type is declared with an enum key. These enum keys can be only an enum or enum class or enum struct. The enum class and enum struct are only supported since C++11.

In C++, we can categorize enumeration into two types unscoped enumeration and scoped enumeration. Unscoped and Scoped enumeration also have some other names, these are the following.

  1. Unscoped enum/ Standard enum / plain enum /  conventional enum.
  2. Scoped enum/ Strong enum/enum class  / new enum / managed enum.

 

So let’s see scoped and unscoped enums one by one….

 

Unscoped enum:

The enumeration type declared with only an “enum” is an unscoped enumeration, and its enumerators are unscoped enumerators.

Consider the below example, in which the enumerators are unqualified enumerators; so they are visible throughout the scope in which the enum is declared.

namespace NON_SCOPED_ENUM_EXAMPLE
{
enum EDryFruit { eAlmond, eCashew, eWalnut};

bool isAlmond(EDryFruit dryFruit)
{
    // Enumerator eAlmond is visible without qualification
    if (dryFruit == eAlmond)
    {
        /*
          Your code
          */
    }
}
}

 

Now let’s discuss the issue with unscoped enum.

An unscoped enum is a distinct type in C++ but they are not type-safe, and sometimes it can be the cause of a serious issue. Consider the below example,

#include <iostream>


int main()
{
    enum EDryFruit
    {
        eAlmond,
        eCashew,
        eWalnut
    };

    enum EAnimal
    {
        eCow,
        eLion,
        eTiger
    };


    EDryFruit dryFruit { eCashew };

    EAnimal animal { eLion };

    const bool isEqual = (dryFruit == animal);

    if (isEqual)
    {
        std::cout << "They are equal\n";
    }
    else
    {
        std::cout << "They are not equal\n";
    }

    return 0;
}

Output: They are equal

 

I think you are also surprised by the output.   Right???????

Don’t worry I will explain. But before explaining why you are getting the above output I just want to revise the basic concept of enum.

In enumeration, if the first enumerator has no initializer, the value of the corresponding constant would be zero, and another enumerator without the initializer gets the increasing value from one of the previous enumerators.

That means in the above example enumerators eAlmond, eCow defines to be zero and eCashew, eLion to be (0+1 => 1) and eWalnut, eTiger to be (1+1 => 2).

So in the above code when animal and dryFruit are compared, the compiler will try to convert the animal and dryFruit to the corresponding integer, to see whether they are equal or not.

Since animal and dryFruit are both set to enumerators that convert to the integer value 1 that is the reason animal will b equal to dryFruit.

This doesn’t make sense semantically because both animal and dryFruit are from different enumerations and are not intended to be comparable. This type of situation could cause hidden issues and destroy your many hours.

 

Another problem with the standard enum is name collision. Consider the below example to understand the name collision issue.

#include <iostream>

namespace EDIBLE_ITEMS
{
enum EDryFruit
{
   eAlmond,
   eCashew,
   eWalnut
};

enum EEdibleItem
{
   eMango,
   eApple,
   eWalnut  //error
};

} // namespace EDIBLE_ITEMS

int main()
{


    return 0;
}

The above source code shows what happens if we add another enumerator which contains an already-used name.

In the code, you can see both enums are contained eWalnut, so name collision will occur and the compiler reports an error.

You can solve the name collision issue by putting the enums in different namespaces. Like the below code example,

namespace EDIBLE_ITEMS
{

enum EEdibleItem
{
    eMango,
    eApple,
    eWalnut
};

}// namespace EDIBLE_ITEMS



namespace EDRY_FRUIT
{

enum EDryFruit
{
    eAlmond,
    eCashew,
    eWalnut
};
}// namespace EDRY_FRUIT


 

Scoped enum:

The enumeration type declared with an “enum class” or “enum struct” is a scoped enumeration, and its enumerators are scoped enumerators. Semantically “enum class” and “enum struct” are equivalent.

Consider the example below, where we must qualify the enumerators eAlmond with EDryFruit.

namespace SCOPED_ENUM_EXAMPLE
{
enum class EDryFruit { eAlmond, eCashew, eWalnut};
bool isAlmond(EDryFruit dryFruit)
{
    //Enumerator eAlmond must be qualified by enum type
    if (dryFruit == EDryFruit::eAlmond)
    {
        /*
          Your code
          */
    }
}
}

 

Scoped enumerations work similarly to unscoped enumerations, but have two primary differences:

1). Scoped enums are strongly typed. That means they won’t implicitly convert to integers. Consider the example below,

#include <iostream>
int main()
{
    //scoped enum
    enum class EDryFruit
    {
        eAlmond,
        eCashew,
        eWalnut
    };
    //scoped enum
    enum class EAnimal
    {
        eCow,
        eLion,
        eTiger
    };

    EDryFruit dryFruit { EDryFruit::eCashew };
    EAnimal animal { EAnimal::eLion };

    const bool isEqual = (dryFruit == animal);
    if (isEqual)
    {
        std::cout << "They are equal\n";
    }
    else
    {
        std::cout << "They are not equal\n";
    }
    return 0;
}

 

When you will compile the code; the compiler report error on line 22, since the scoped enumeration won’t convert to any type implicitly.

 

2.)  Scoped enums are strongly scoped; the enumerator name must be qualified by the enum type name. See the below example code,

#include <iostream>
int main()
{
    //scoped enum
    enum class EDryFruit
    {
        eAlmond,
        eCashew,
        eWalnut
    };


    // compile error: eCashew not defined in this scope region
    EDryFruit dryFruit { eCashew };

    return 0;
}

The compiler report error at line number 14.

 

Recommended Post

Leave a Reply

Your email address will not be published. Required fields are marked *