The enum type is unscoped prefer enum class over enum?

Viewed 4378

and I'm new to C++ and I'm wondering if anyone can help me to understand why

enum difficulty { NOVICE, EASY, NORMAL, HARD, UNBEATABLE };
difficulty myDiffiuclty = EASY

and

enum shipCost { FIGHTER_COST = 25, BOMBER_COST, CRUISER_COST = 50 };
shipCost myShipCost = BOMBER_COST;

are underlined in green? it says it prefers enum class, but when I change it to enum class then

enum class difficulty { NOVICE, EASY, NORMAL, HARD, UNBEATABLE };
difficulty myDiffiuclty = EASY;

enum class shipCost { FIGHTER_COST = 25, BOMBER_COST, CRUISER_COST = 50 };
shipCost myShipCost = BOMBER_COST;

EASY becomes underlined in red and myShipCost is underlined in green, BOMBER_COST underlined in red and CRUISER_COST is underlined in red

const int ALIEN_POINTS = 150;
int aliensKilled = 10;
int score = aliensKilled * ALIEN_POINTS;
cout << "score: " << score << endl;

enum difficulty { NOVICE, EASY, NORMAL, HARD, UNBEATABLE };
difficulty myDifficulty = EASY;

enum shipCost { FIGHTER_COST = 25, BOMBER_COST, CRUISER_COST = 50 };
shipCost myShipCost = BOMBER_COST;
cout << "\nTo upgrade my ship to a cruiser will cost "
    << (CRUISER_COST - myShipCost) << " Resource Points.\n";

system("pause");
return 0;
2 Answers
enum class difficulty { NOVICE, EASY, NORMAL, HARD, UNBEATABLE };
difficulty myDiffiuclty = difficulty::EASY;

enum class shipCost { FIGHTER_COST = 25, BOMBER_COST, CRUISER_COST = 50 };
shipCost myShipCost = shipCost::BOMBER_COST;

with enum class, implicit conversion to/from integers is removed, and you have to scope the constants in it.

This is considered an improvement. Explicitly casting it back to an integer still works, but it doesn't happen by accident.

std::cout << "\nTo upgrade my ship to a cruiser will cost "
    << (static_cast<int>(shipCost::CRUISER_COST) - static_cast<int>(myShipCost)) <<
    " Resource Points.\n";

system("pause");
return 0;

once you are using enum class, you should rename them:

enum class difficulty { novice, easy, normal, hard, unbeatable };
difficulty myDiffiuclty = difficulty::easy;

enum class shipCost { fighter =25, bomber=30, cruiser = 50 };
shipCost myShipCost = shipCost::bomber;

as the ship part of the name is now in the enum, no need to repeat it in the constant. Similarly, because the names are scoped, you don't have to SHOUT them.

Note, however, if you are just using it to create constants and no intention to create a type, consider using something like:

namespace ship {
  namespace cost {
    constexpr int fighter = 25;
    constexpr int bomber = 30;
    constexpr int cruiser = 50;
  }
}

now we have ship::cost::fighter as an int that is compile-time calculated, instead of as an enum class.

Now that you understand the difference, from the answers above, I'd like to point out a couple of places where the "new" way of doing things breaks down, and what you can do about it.

Consider this (abbreviated) actual example I ran into, porting some audio software I wrote in C to modern C++ (C++17):

typedef enum sample_type_e {
   sample_type_uint16,
   sample_type_double
}sample_type_t;

Now, the first thing to know is that the typedef here is no longer required; you can read more about that elsewhere. Moving on...

This code, compiled in Visual Studio 2019, will result in the error you describe.

Now, one approach to fixing this would seem to be to move to the new syntax-- but watch what happens:

enum class SampleType {
   uint16,
   double
}

Oops.

That's not going to work-- double is a reserved word. Now, you could, in exasperation, do this:

enum class SampleType {
   uint16,
   type_double
}

...but that's not very consistent, and by the time you get to this:

enum class SampleType {
   type_uint16,
   type_double
}

...you have arguably achieved the exact opposite of at least one of the purported benefits of this new feature: being terse, where possible, without losing context.

The nuisance in my supposedly-contrived but actually real-world example is that you can't scope a reserved word: they're always reserved. (Tangent point of interest: there are languages where that is never true...)

This is called "being unlucky"; it happens a lot more often than language designers like to admit, and it always comes up when you don't have one of them in the room with you.

So, if something like that happens to come up for you, and you just really don't like what the results of trying to use "class enum" wind up looking like, what do you do?

Well, it turns out you can probably make the entire original problem go away by using another language feature that is considered good practice to use wherever possible: namespacing. If I merely namespace that original declaration (minus the crufty typedef), I get:

namespace audiostuff {
    enum sample_type_e {
       sample_type_uint16,
       sample_type_double
    };
};

...and as long as any code using that stuff is either in the same namespace, or has

using namespace audiostuff;

at the top, or, alternately, uses is like this:

audiostuff::sample_type_e my_sample_type_var;

...then, lo and behold, Visual Studio stops complaining-- and other compilers should as well.

The complaint, after all, is about reminding you that you should avoid cluttering up the global namespace-- especially by accident. "class enum" is one way to do that, but using an explicit namespace is as well.

Related