Are unscoped enumerations still useful?
Asked Answered
F

2

10

I haven't found any wording in the C++11 standard that says unscoped enums are deprecated, but from a pragmatic perspective I'm wondering if they are still useful. A lot of people on my team have gotten in the habit of converting unscoped enums to scoped enums, but it's caused some headache:

class foo
{
public:
    enum MyEnum { One, Two, Three };
};

They convert this to:

class foo
{
public:
    enum class MyEnum { One, Two, Three };
};

Which means when these enumerators are used, instead of foo::One, it looks like foo::MyEnum::One. I've been asking the following best-practices:

  1. If you convert to scoped enum, move it out of classes and into global scope or namespace scope (to improve usability and avoid the latter usage example above).
  2. If you keep the enum unscoped, make sure it's in namespace/class/function/etc scope so that it doesn't conflict with other names.

The main difference between the two points is that for #1 we don't put them in classes, which otherwise adds some verbose indirection.

All of this seems like over complication, and seems like it would be much simpler to just keep enumerations already in classes as unscoped enums. What's the general best-practice approach for deciding between the two?

Firstrate answered 5/12, 2014 at 16:38 Comment(9)
The advantage of switching to enum class is everything you can read about why it was added; the disadvantage of switching to enum class is everything you wrote above. That doesn't tell us what to do when writing our code from scratch, though.Striate
What's the problem with the second usage?Schizophyceous
@Schizophyceous Verbosity. It's like using 2 nested namespaces for your objects when only 1 would suffice.Firstrate
"just keep enumerations already in classes as unscoped enums" - that's sometimes sensible, and sometimes not. What if you want two enumerations in the same class?Cede
@MikeSeymour I think the issue (at least for our legacy code base) is that enums most times were added to classes to simulate what scoped enumerations provide today, so we could do Class::Enumerator. So, when all that changes is the addition of the class keyword, things become too verbose for no real reason: Class::Enumeration::EnumeratorFirstrate
scoped enums is creaed for type-safe, personally i don't like it. old enum is much better atleast you don't need static-cast any wherePolysyndeton
@MORTAL: If you're static_casting your scoped enums then you missed the point entirely and your code is wrong.Striate
The reason you can't find anything saying they're deprecated is because they're not deprecated, and that's because they're still useful. Scoped enumerations are different things, and useful in different places, they definitely don't replace all uses of unscoped ones.Buehrer
Also scoped enums are unique on their own. Does putting any of them inside a class declaration help?Susannasusannah
T
13

Scoped enumerators cannot implicitly convert to their underlying type. If you need your enum values to implicitly convert to their underlying type, you cannot use a scoped enumerator.

An example of when this is useful is when you are talking to an API out of your control, and your enum values are bit flags. The API (you don't control) that expects an uint32_t or some other integral type as a bit flag.

You can override operator| etc in order to keep everything "in type", or have them generate the underlying type -- but a single element of your enum class cannot implicitly convert to uint32_t.

Another use for unscoped enums I find useful is to replace #define FOO 32 style macros. Instead of textual substitution, I get a token that has the same meaning, and I don't have to rewrite the code base. If there are a tightly grouped set of such values, I can eventually reach the point where I can change the int arguments that are expecting such #define tokens to be passed with enum values, and the parameters are now typed!

This allows gradual migration towards a better code base.

The next step might be to use scoped enums for those values, but the overhead of having to do everything at once can mean that the first step may not be taken. The perfect is the enemy of the good.


On the other hand, if your enums are really just a set of enumerated values, and their value in the underlying type is unimportant, then scoped enums are almost always a better idea than unscoped enums. They prevent accidental conversion to the underlying type: if the value in the underlying type is merely an implementation detail, such conversion can cause bugs.

This is by far the most common use case I find for enums -- a list of distinguished values, whose underlying type and value is merely an implementation detail.

Thalassa answered 5/12, 2014 at 17:41 Comment(0)
O
0

They are useful if you prefer weak typing for your use case. Since unscoped enums also allow for implicit conversion to the underlying type and work just like numbers, they make the code feel less abstract. This makes life easier if you have lots of constants for bit masking inside enumerators, for example. You can't directly apply bitwise logic or arithmetic to an enum class value, but enum will work just like an integer type. Also you don't have to prefix values with "MyEnum::", what is handy for frequently used enums.

If you still need a scoped enum that implicitly converts to an integer, just put a regular unscoped enum inside a struct that will keep just this enum:

struct MyEnum {
    enum MyEnum { One, Two, Three };
};
Outwash answered 25/5 at 19:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.