6

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} );
x-x
  • 7,287
  • 9
  • 51
  • 78
  • One option is to consider a bit field struct. I believe the compiler will generate appropriate/portable code even if you exceed 64 bits. – Yasser Asmi Mar 14 '14 at 01:36
  • 1
    AFAIU, an `enum`'s value is limited to the word size, i.e., it could be limited to 16 bits. – vonbrand Mar 14 '14 at 01:36
  • Our of curiosity, what are you doing that requires you to have more 64 unique flags? – Colin Basnett Mar 14 '14 at 02:56
  • @cmbasnett, It's for a game. – x-x Mar 14 '14 at 03:35
  • I see you've made 13 revisions and now the post is "community wiki." If you want to earn points from upvotes, flag it and ask the moderators to remove the community wiki bit. – Potatoswatter Mar 14 '14 at 10:12
  • 3
    enum is not the right answer to your problem – knivil Mar 14 '14 at 10:21
  • Is there anything more to answer here? It looks like you're well on your way. Note that you won't be able to modify the `bitset` after it's constructed unless you overload operators, and for that I'd recommend deriving from `bitset`. Also, note that the `initializer_list` may be longer than `bit_count` if duplicates are allowed, but you are not checking for duplicates in the loop. – Potatoswatter Mar 14 '14 at 17:47

3 Answers3

6

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

Community
  • 1
  • 1
Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • How could I create a mask like the example in the question? `auto mask = std::bitset<128>{ foo, bar, baz }` does not work. – x-x Mar 14 '14 at 06:07
  • 1
    @DrTwox If `foo`, `bar`, and `baz` are the enumerators, you would write `auto mask = foo | bar | baz;`. It works exactly the same as the code in the question, not sure why you want a braced-init-list. See [IDEone link](http://ideone.com/kJ271Z) from the linked answer. – Potatoswatter Mar 14 '14 at 06:47
  • [It seems to fall apart when applied to my problem though](http://ideone.com/TAgSI0). The output is not correct. The braced initializer list constructor is what `std::bitset` is missing that would make it work in this situation. – x-x Mar 14 '14 at 07:13
  • @DrTwox The left shift operation (line 28) is overflowing. Needs to be replaced with a `bitset` member call. I didn't write that code specifically to handle large bitsets. – Potatoswatter Mar 14 '14 at 07:17
  • Thank you! Could this create a `static const` `std::bitset` calculated at compile time? – x-x Mar 14 '14 at 23:09
  • @DrTwox No, but the only reason not is because `set()` is not `constexpr`. The original from the other answer does allow compile-time calculation. This is a defect in the standard and I'll report it. – Potatoswatter Mar 15 '14 at 00:22
3

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 );
}
Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • 2
    Very low quality post. No example code and not even a link to the documentation. – ThiefMaster Mar 14 '14 at 07:38
  • @ThiefMaster: on the contrary, *as you are well aware*, this is the highest quality answer. anything more than identifying the solution does not help the OP, and constitutes noise. but **thanks for showing yourself**. – Cheers and hth. - Alf Mar 14 '14 at 07:48
  • It doesn't answer the actual question asked. – x-x Mar 14 '14 at 08:12
  • At the time this was posted, `bitset` was not mentioned in the question, so this was an answer albeit a spartan one. – Potatoswatter Mar 14 '14 at 10:08
  • I'm pretty sure a key part of the question was also how to define the enumerations themselves. if `baz` is `1ull<<63`, then how to define the _next_ flag? – Mooing Duck Mar 14 '14 at 18:27
  • @MooingDuck: there's no need to define those bitmap values as integral constants. instead, if desired, they can be defined as `std::bitmap` values. like, u now, use `std::bitmap`. apparently i illustrated that just before you posted your comment. coincidence. – Cheers and hth. - Alf Mar 14 '14 at 18:49
  • @Cheersandhth.-Alf: Well obviously that's the answer. However, despite your claim that you've illustrated that, nowhere in your answer nor comments do you show or suggest how to define the flags as bitmap values. Since that was his primary problem even in the original question, your answer (though correct) is unclear, and leaves a lot of experimentation to stumble on the right answer. – Mooing Duck Mar 14 '14 at 18:53
  • @MooingDuck: you're describing the most trivial as problematic. perhaps some non-programmer reader might believe you're sincere. i don't – Cheers and hth. - Alf Mar 14 '14 at 21:18
  • 1
    @Cheersandhth.-Alf: A guy trying to mix initializer lists with bitsets might not realize that `auto newflag = std::bitset(1)< – Mooing Duck Mar 14 '14 at 21:21
  • 1
    @MooingDuck: that's a quite overcomplicated way to do the most trivial thing. also, when the guy wrote the question he didn't know about `std::bitset`. with my answer, presumably he looked it up. then, having looked it up, one needs to be sort of intellectually challenged in order to not understand how to call a member function like `set`. there aren't that many of them. so, if were serious, that's the judgment you pass on the OP: dumb as a brick. but i don't think he is, and i don't think you are being sincere. – Cheers and hth. - Alf Mar 14 '14 at 21:35
  • 1
    I can't think of a simpler way to do it, and have them be compiler constants still. Sacrificing compiler constants, you could initialize them at runtime with `.Set` I suppose. – Mooing Duck Mar 14 '14 at 21:47
  • @MooingDuck: meaningless babble you resort to. i guess, again, it might sound meaningful to a non-programmer reading this. i wonder who you think your audience is. – Cheers and hth. - Alf Mar 14 '14 at 22:31
  • Thank you for updating the answer to be more immediately useful. I apologise if my question was not clear enough. Yes, I did already know about `std::bitset`, but the original question had nothing to do with that, it was a simple yes/no question about the possibility of making an enumeration larger than 64 bits. Anything more than answering that question does not help me and constitutes noise. – x-x Mar 14 '14 at 23:02
  • 1
    @DrTwox: All three answers so far, including this one, treat your question as an **X/Y-question**. That means, asking about your imagined solution Y (large value enumeration) to the real issue X (large bitset). Neither potatoswapper nor I thought it necessary to explicitly state that your Y isn't supported. If that really is the only information that you now, on reflection, think helps you, that your Y isn't on, and that info about how to solve your described real problem X isn't helpful, indicating that it wasn't *really* the real problem, then I suggest asking more in line with reality. – Cheers and hth. - Alf Mar 14 '14 at 23:52
3

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;
}
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • OK, better :) Note that `1ULL` is guaranteed to be at least 64 bits as well. And, just for illustration, how can you declare `foo` and `baz`? – Potatoswatter Mar 14 '14 at 03:16
  • I think the key part of the problem is how to define `components::foo` where `components::foo` ought to be `1<<70`? – Mooing Duck Mar 14 '14 at 18:24
  • @MooingDuck there is no way to do that w/o a 128 bit type, which gcc does support but is not portable. The question has evolved a great deal since it was first asked. At this point, I don't think enumerations is the correct approach. Honestly, it should have been a second question after the original was answered. – Shafik Yaghmour Mar 14 '14 at 18:38
  • @ShafikYaghmour: The current question, the original question, and the title of the question, all make reference to the fact he can't make a enumeration with the 65+th bits set. That's what this question is all about – Mooing Duck Mar 14 '14 at 18:40