Jump to content
  • Advertisement
Sign in to follow this  
TheComet

C++ Why are enums broken

Recommended Posts

Posted (edited)
enum class Thing {
    FOO = 0x01, 
    BAR = 0x02
};

Thing thing = Thing::FOO | Thing::BAR; // doesn't compile

This is an age old problem. What is the proper way to achieve what I'm trying to do here? As far as I can tell, using enum class is more a hindrance than a useful tool with flags. I end up typing less with a normal enum:

enum Thing {
    FOO = 0x01, 
    BAR = 0x02
};

unsigned char thing = FOO | BAR;

 

Edited by TheComet

Share this post


Link to post
Share on other sites
Advertisement

This is the right behavior for an enum class. Normal enums are integers and can be used in the same way as integers, but this can cause problems. Imagine you have an enum that holds read/write flags for file I/O. With regular enums you can do something like READ + 5, but what does that mean? How can you add 5 to something that represents the concept of opening a file for reading?

Enum classes get around this issue by disallowing most operators by default. You have to overload the right operators for your use cases. For your case you need to overload operator|.

Share this post


Link to post
Share on other sites

You could also investigate using std::bitset to represent flags instead of manually or/anding the bits. You can still use an enum for this, but then each enum value would just be a name for a bit.

Share this post


Link to post
Share on other sites
Posted (edited)

enum classes enforce that you cannot construct new values from them, unless you cast. The primary reason is to make it explicitly visible in the source code that you're using enum names as integer values rather than as a symbolic name (where the value is not very relevant). In other words, it eliminates the gap of using enum values as hidden constants.

C++ being C++, it's of course not impossible, you can add casts, and hide such things in an overloaded operator.

The point is however that you should consider whether your enum class is really an enum class, or that a set of (integer) constants would be more fitting.

Edited by Alberth

Share this post


Link to post
Share on other sites

I would not use an enum at all here, class or otherwise.  The quick and dirty way to do this is to just use constants:

namespace Thing {
  constexpr uint8_t FOO = 0x01;
  constexpr uint8_t BAR = 0x02;
}

The fancy way is to define a full (non-enum) class that implements operator |, operator &, and ideally an operator <<(std::ostream&, Thing) that provides a human-readable representation (e.g. "FOO|BAR" instead of "3").

Share this post


Link to post
Share on other sites
13 hours ago, a light breeze said:

I would not use an enum at all here, class or otherwise.  The quick and dirty way to do this is to just use constants:


namespace Thing {
  constexpr uint8_t FOO = 0x01;
  constexpr uint8_t BAR = 0x02;
}

The fancy way is to define a full (non-enum) class that implements operator |, operator &, and ideally an operator <<(std::ostream&, Thing) that provides a human-readable representation (e.g. "FOO|BAR" instead of "3").

The problem with this approach is that it doesn't create a type or type alias that you can use to cue in what values are actually expected for a given parameter or variable. If I have a function, I can say that the type of one argument is Thing and then i know that Thing::FOO and Thing::BAR are valid options. Can't do that if Thing is a namespace.

I get around this with some magic macros that create a struct named Thing with an int cast, and create the enum values inside that struct. 

Share this post


Link to post
Share on other sites
1 hour ago, Promit said:

The problem with this approach is that it doesn't create a type or type alias that you can use to cue in what values are actually expected for a given parameter or variable. If I have a function, I can say that the type of one argument is Thing and then i know that Thing::FOO and Thing::BAR are valid options. Can't do that if Thing is a namespace.

I get around this with some magic macros that create a struct named Thing with an int cast, and create the enum values inside that struct. 

You could use BOOST_STRONG_TYPEDEF or something similar, but I really wish that strong or opaque typedefs were part of the language.

 

 

Share this post


Link to post
Share on other sites
On 6/9/2018 at 5:51 PM, TheComet said:

As far as I can tell, using enum class is more a hindrance than a useful tool with flags.

That's correct.  The purpose of strongly typed enums was to allow strict enforcement of enumerations. It prevents things like (Blue+5) which may not be a valid enumeration value, or (Walking | Yellow) which may not be a valid combination. You can list all the states in your FSA and be sure you're not getting unexpected values that aren't enumerated at all.

Flag operations are directly contrary to the strong type since they're asking to be treated as bit patterns rather than enumerations. That is, you are not using them like a strongly typed enumeration where you're only dealing with the items on the list, instead you're treating them as a pattern of bits.

 

One pattern that can help keep them within a name is to keep them as weakly typed values inside a namespace:

namespace MyFlags {
  enum MyFlags : int {
    none = 0,
    foo = 1<<0,
    bar = 1<<1,
    baz = 1<<2,
    thing1 = 1<<3,
    thing2 = 1<<4,
    ...
  };
}

 

Alternatively you can use a mix of templates and/or macros to generate the expected operations for your flags.  There are plenty of examples of it online that compile away to constant expressions that are optimized back down to bitfield operations, my first search pulls up four different github projects for it and a bunch of stackoverflow and similar postings.

Just remember that you aren't using the feature the way it was designed.  

If you want bit manipulation then use bit manipulation. If you want a strong enumeration where you're only dealing with the enumerated values, use an enum class. They're different use cases and they have different meanings and different requirements.

Share this post


Link to post
Share on other sites
struct MyBitField {
  unsigned int foo : 1;
  unsigned int bar : 1;
  ...
};

You could also do bit fields. They can tell you exactly your intend. Just another idea.

Share this post


Link to post
Share on other sites
3 hours ago, dragongame said:

You could also do bit fields.

You can do that if you don't care about the specific bits involved.  The language standard says the allocation order for the bits is implementation defined.  You don't know which bits are assigned to which positions so everything needs to be done by name. 

Different ABIs have different bit ordering. If you're using the x64 ABI it is packed like this, and you'll note it can include spaces between items. Other platform ABIs have different packing requirements, and it looks like GCC supports six variants depending on the target platform.

 

True they are still bit flags, but beware they may not be the human-intuitive values that other programmers expect.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!