In my entity component system I track and query which components each entity has using a bit mask.
// Thanks to Shafik Yaghmour for the macro fix
#define BIT(x) (static_cast<std::uint64_t>(1) << x)
enum class components : std::uint64_t
{
foo = BIT( 0),
bar = BIT( 1),
// Many more omitted
baz = BIT(63)
};
// Operator | is used to build a mask of components. Some entities
// have dozens of components.
auto mask = components::foo | components::bar | components::baz
// Do something with the entity if it has the requisite components.
if ( entity->has_components( mask ) ) { ... }
I've hit the 64 bit limit of the enumeration. Can a C++ enumeration (portably) be made bigger than 64 bits?
Update 1: I'm aware of std::bitset
, but I can't create a mask like auto mask = std::bitset<128>{ components::foo, components::baz }
because std::bitset
doesn't have a constructor that takes a std::initializer_list
. If your answer tells me to use std::bitset
please demonstrate this usage or similar.
Update 2: I hacked up a function to create a std::bitset
from a std::initializer_list
:
enum class components : std::size_t
{
foo,
bar,
baz,
count
};
template< typename enumeration, std::size_t bit_count = static_cast< std::size_t >( enumeration::count ) >
std::bitset< bit_count > make_bitset(std::initializer_list< enumeration > list)
{
assert(list.size() <= bit_count);
std::bitset< bit_count > bs{};
for (const auto i : list)
{
bs.set(static_cast< std::size_t >(i));
}
return bs;
}
// Which can create the mask like so:
auto mask = make_bitset< components >( {components::foo, components::baz} );
As others noted, the storage must be a std::bitset
. But how to interface this to enumerations? Use operator overloading as outlined in this answer. The enumerators only need to store the bit index number, which buys you a little more space ;) .
Note, that code must be expurgated of shift operations, namely:
flag_bits( t bit ) // implicit
{ this->set( static_cast< int >( bit ) ); }
http://ideone.com/CAU0xq
Note, you current code has undefined behavior, the integer literal 1
has type int here:
#define BIT(x) (1 << x)
^
which will probably be 32 bits
on most modern systems and so shifting by 32 bit
or more is undefined behavior according to the draft C++ standard section 5.8
Shift operators which says:
The behavior is undefined if the right operand is negative, or greater than or equal to the length in bits of the promoted left operand.
The fix for this would be to use static_cast to convert 1
to uint64_t:
#define BIT(x) (static_cast<std::uint64_t>(1) << x)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Or as Potatoswatter points out you can use ULL
suffix which is guaranteed to be 64 bit
.
There is no portable way to use a larger type than uint64_t, some compilers such as gcc provide for a 128 bit
integer type but again that is not portable.
std::bitset should provide the interface you need. You can use set to set any bit to any value and test to test the value of a specific bit. It provides binary or which should provide this functionality:
auto mask = components::foo | components::baz
for example:
#include <bitset>
#include <iostream>
int main()
{
std::bitset<128> foo ;
std::bitset<128> baz ;
foo.set(66,true ) ;
baz.set(80,true ) ;
std::cout << foo << std::endl ;
std::cout << baz << std::endl ;
std::bitset<128> mask = foo | baz ;
std::cout << mask << std::endl ;
return 0;
}
Use a std::bitset
.
Addendum:
After I wrote the above answer the question has been updated with mention of std::bitset
and some ungood ideas for how to use it, so far with 13 revisions.
I failed to foresee that there could be any difficulty using std::bitset
, sorry. For it's just matter of not introducing needless complexity, of abstaining from that. For example, instead of the now suggested
#include <bitset>
enum class components : std::size_t
{
foo,
bar,
baz,
count
};
template< typename enumeration, std::size_t bit_count = static_cast< std::size_t >( enumeration::count ) >
std::bitset< bit_count > make_bitset(std::initializer_list< enumeration > list)
{
assert(list.size() <= bit_count);
std::bitset< bit_count > bs{};
for (const auto i : list)
{
bs.set(static_cast< std::size_t >(i));
}
return bs;
}
auto main() -> int
{
auto mask = make_bitset< components >( {components::foo, components::baz} );
}
just do e.g.
#include <bitset>
namespace component {
using std::bitset;
enum Enum{ foo, bar, baz };
enum{ max = baz, count = max + 1 };
using Set = bitset<count>;
auto operator|( Set const s, Enum const v )
-> Set
{ return s | Set().set( v, true ); }
} // namespace component
auto main() -> int
{
auto const mask = component::Set() | component::foo | component::baz;
}
or simply, without syntactic sugaring,
#include <bitset>
namespace component {
using std::bitset;
enum Enum{ foo, bar, baz };
enum{ max = baz, count = max + 1 };
using Set = bitset<count>;
} // namespace component
auto main() -> int
{
auto const mask = component::Set().set( component::foo ).set( component::baz );
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With