Reputation: 13
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
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 enum
type 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
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
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