Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can a C++ enumeration be made bigger than 64 bits?

Tags:

c++

enums

c++11

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} );
like image 971
x-x Avatar asked Mar 14 '14 01:03

x-x


3 Answers

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

like image 146
Potatoswatter Avatar answered Sep 20 '22 19:09

Potatoswatter


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;
}
like image 41
Shafik Yaghmour Avatar answered Sep 19 '22 19:09

Shafik Yaghmour


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 );
}
like image 40
Cheers and hth. - Alf Avatar answered Sep 20 '22 19:09

Cheers and hth. - Alf