barwin
barwin

Reputation: 13

C++ enum flag usage

I've googled around but there doesn't seem to be a clear answer immediately available. I'm trying to figure out the proper usage of enum flags in C++. I'm working on a 2D tile based platformer engine and I want to give each tile flags that determine which edges can be collided with.

In one file I have:

enum CollidableEdges
{
    TopEdge = 1,
    RightEdge = 2,
    BottomEdge = 4,
    LeftEdge = 8
};

How do I actually call these flags in an object and use them in logic later on? In the examples I've looked at online it doesn't seem to declare an enum anywhere besides the initial list when manipulating flags. Can someone clarify what exactly is happening and how to use it? For example, when creating a new Tile object with a definition something like this:

class Tile: public Entity
{
    public:
        Tile(std::string obstacleTexture, int, int);
}

I'm a fairly new programmer, so I'm sure I'm using bad practices or am missing something obvious. Don't be shy in telling me.

Upvotes: 0

Views: 691

Answers (3)

πάντα ῥεῖ
πάντα ῥεῖ

Reputation: 1

Can someone clarify what exactly is happening and how to use it?

To initialize particular bits

uint8_t flags = uint8_t(TopEdge) | uint8_t(LeftEdge);

To set particular bits

flags |= uint8_t(BottomEdge);

To clear particular bits

flags &= ~(uint8_t(TopEdge) | uint8_t(LeftEdge));

To test for particular bits

if(flags & uint8_t(BottomEdge) > 0) { // Flag is set (BottomEdge)
}

The reason you need to cast it, is because if you have an expression like TopEdge | LeftEdge the compiler won't accept the original enumtype as result.

To get rid of the casts, you can also define an overloaded operator|(),operator&(),etc.:

CollidableEdges operator|(CollidableEdges  left, CollidableEdges right) {
    return static_cast<CollidableEdges>(static_cast<unsigned>(left) | 
                                        static_cast<unsigned>(right));
}
// etc.

which allows to write simply

CollidableEdges edges = TopEdge | LeftEdge;

NOTE:
Though the above statements will work fine, IMHO they give bad readability for the intended semantics. I personally prefer the following solution:

 enum class CollidableEdgeBitPos : size_t {
    TopEdge ,
    RightEdge ,
    BottomEdge ,
    LeftEdge 
 };

 typedef std::bitset<4> CollidableEdges;

 CollidableEdges edges;

 edges[CollidableEdgeBitPos::RightEdge] = true;
 edges[CollidableEdgeBitPos::BottomEdge] = false;

Upvotes: 1

James Kanze
James Kanze

Reputation: 153919

Given the numerical values, it looks like your enum is being used to define bit masks. In such cases, the usual procedure would be to overload the relevant operators for them, so that you can use them appropriately:

inline CollidableEdges
operator|( CollidableEdges lhs, CollidableEdges rhs )
{
    return static_cast<CollidableEdges>(
        static_cast<unsigned>( lhs) | static_cast<unsigned>( rhs ) );
}

etc. (You'll want |, |=, &, &= and ~.) These operators should generally go in the same header as the enum definition. Objects which use them would then also take a CollidableEdge as an argument:

Tile::Tile( std::string const& texture, CollidableEdge edges )...

etc.

Traditionally, the names of the enumeration constants are defined in the same scope as the enum itself; in C++11, you also have the possibility of restricting them to the enum, by declaring enum class CollidableEdge, rather than enum CollidableEdge; in that case, you would have to specify CollidableEdge::TopEdge | CollidableEdge::LeftEdge, rather than just TopEdge | LeftEdge. (On the other hand, it means that they won't collide with any other symbols you might want to define.)

Finally, given the "wrong" precedance of | and &, I've often found it convenient to define the operators +, - and * as well, with a + b the equivalent of a | b (you're adding values to the set), a - b the equivalent of a & ~ b (you're subtracting values from the set) and a * b the same as a & b.

Upvotes: 0

jsantander
jsantander

Reputation: 5102

CollidableEdges is a type you can use it anywhere you can use a type (e.g. it could be the type of one of the parameters in your method.

The labels of the enum are identifiers valid in the containing scope of the enum. For example, if you define the enum within a class XX, the labels can be reached using XX::TopEdge

You can cast to/from int, but it is best to check (e.g. switch/case) instead of blind cast from int.

Casting to int results in the value associated to the label. This is an alternative to static const integer constants. So for example, in your example where each label is associated a bit-wise disjoint value, you can combine the labels using the bitwise or (e.g TopEdge|LefEdge) [but only because they are disjoint integer values).

Upvotes: 0

Related Questions