C++枚举类型是否可以大于64位?

6

在我的实体组件系统中,我使用位掩码来跟踪和查询每个实体具有哪些组件。

// 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 ) ) { ... }

我已经达到了枚举的64位限制。C++枚举是否可以(可移植地)扩大到超过64位?

更新 1: 我知道 std::bitset,但是我无法创建像 auto mask = std::bitset<128>{ components::foo, components::baz } 这样的掩码,因为 std::bitset 没有一个可以接受 std::initializer_list 的构造函数。如果您的答案告诉我要使用 std::bitset,请演示此用法或类似用法。


更新2:我编写了一个函数,可以从std::initializer_list创建一个std::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;
}

// Which can create the mask like so:
auto mask = make_bitset< components >( {components::foo, components::baz} );

一个选择是考虑使用位域结构体。我相信即使超过64位,编译器也会生成适当/可移植的代码。 - Yasser Asmi
1
据我所知,enum的值受限于字长,也就是说,它可能限制在16位。 - vonbrand
出于好奇,你在做什么需要超过64个唯一标志的事情? - Colin Basnett
我看到你已经进行了13次修订,现在这篇文章是“社区维基”了。如果你想通过点赞获得积分,请标记它并请求管理员删除社区维基标识。 - Potatoswatter
3
枚举不是解决你的问题的正确答案。 - knivil
显示剩余2条评论
3个回答

6
正如其他人所指出的,存储必须是一个std :: bitset。但是如何将其与枚举接口?按照此答案中概述的方式使用运算符重载。枚举器只需要存储位索引号,这可以为您节省一些空间 ;)。
请注意,代码必须删除移位操作,即:
flag_bits( t bit ) // implicit
    { this->set( static_cast< int >( bit ) ); }

http://ideone.com/CAU0xq


我该如何创建一个像问题中示例那样的掩码?使用auto mask = std::bitset<128>{ foo, bar, baz }无法实现。 - x-x
1
如果foobarbaz是枚举类型,那么您可以编写auto mask = foo | bar | baz;。这与问题中的代码完全相同,不确定为什么您需要一个花括号初始化列表。请参见链接答案中的IDEone链接 - Potatoswatter
尽管如此,当应用于我的问题时,它似乎会崩溃(http://ideone.com/TAgSI0)。输出不正确。花括号初始化列表构造函数是`std::bitset`缺少的东西,在这种情况下它能够正常工作。 - x-x
@DrTwox 左移操作(第28行)溢出了。需要用 bitset 成员调用替换。我没有特别编写该代码以处理大型位集。 - Potatoswatter
谢谢!这个能够在编译时创建一个static const std::bitset吗? - x-x
@DrTwox 不,但唯一的原因是set()不是constexpr。另一个答案中的原始代码确实允许在编译时计算。这是标准中的缺陷,我会报告它。 - Potatoswatter

3

使用 std::bitset.


附加说明:
在我写上面的答案之后,问题已经更新,提到了std::bitset以及一些不好的使用方式,目前有13个修订版本。

我没有预见到使用 std::bitset 会有任何困难,很抱歉。因为它只是避免引入不必要的复杂性,避免那种情况。例如,可以使用以下方法替代现在建议的方法:

#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} );
}

仅仅做例子

#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;
}

或者,不使用语法糖的简单表达方式是:
#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 );
}

2
非常低质量的帖子。没有示例代码,甚至没有链接到文档。 - ThiefMaster
1
@Cheersandhth.-Alf:一个试图将初始化列表与位集混合的家伙可能没有意识到 auto newflag = std::bitset<MAX>(1)<<BITNUM; 就是答案。特别是因为他写这个问题是因为他无法弄清如何做到这一点。 - Mooing Duck
1
@MooingDuck:这是一种过于复杂的方式来完成最简单的事情。此外,当那个人写问题时,他不知道std::bitset。通过我的答案,他可能会查找它。然后,在查找了它之后,一个人需要有点智力挑战才能不理解如何调用像set这样的成员函数。它们并不多。所以,如果你是认真的,那么你对OP的评价就是愚蠢。但我不认为他是,我也不认为你是真诚的。 - Cheers and hth. - Alf
1
我想不出更简单的方法,而且它们仍然可以是编译器常量。如果牺牲编译器常量,你可以使用.Set在运行时初始化它们。 - Mooing Duck
1
@DrTwox:到目前为止,包括这个答案在内的所有三个答案都将您的问题视为一个X/Y问题。也就是说,询问您想象中的解决方案Y(大值枚举)对于真正的问题X(大位集)的影响。Potatoswapper和我都认为没有必要明确说明您的Y不受支持。如果您现在反思后认为唯一有用的信息是您的Y不可行,并且关于如何解决您描述的真实问题X的信息并不有用,表明它并不是真正的问题,那么我建议您更符合实际情况地提出更多问题。 - Cheers and hth. - Alf
显示剩余9条评论

3

请注意,您当前的代码存在未定义行为,整型字面量1在此处具有int类型:

 #define BIT(x) (1 << x)
                 ^

在大多数现代系统上,它的长度可能为32位,因此根据C++标准草案第5.8节“移位运算符”的规定,移动32位或更多位是未定义的行为:5.8。该部分说:

如果右操作数为负数或大于或等于提升后的左操作数的位数,则其行为是未定义的。

修复此问题的方法是使用static_cast1转换为uint64_t
#define BIT(x) (static_cast<std::uint64_t>(1) << x)
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

如Potatoswatter指出的,您可以使用ULL后缀,它保证是64位

没有可移植的方法来使用比uint64_t更大的类型。一些编译器(例如gcc)提供了128位整数类型,但同样不具有可移植性。

std::bitset应该提供您需要的接口。您可以使用set将任何位设置为任何值,test以测试特定位的值。它提供二进制或,这应该提供此功能:

auto mask = components::foo | components::baz

例如:
#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;
}

好的,更好了 :) 请注意,1ULL 至少保证有 64 位。为了举例说明,您如何声明 foobaz - Potatoswatter
我认为问题的关键部分是如何定义components::foo,其中components::foo应该是1<<70 - Mooing Duck
@MooingDuck,没有128位类型是无法完成这个操作的,虽然gcc支持但不具备可移植性。自问题最初提出以来,问题已经发生了很大变化。现在,我认为枚举不是正确的方法。老实说,这应该是在原问题回答后再提出的第二个问题。 - Shafik Yaghmour
@ShafikYaghmour:当前问题、原始问题和问题标题都提到了他无法设置第65位及以上位的枚举。这就是这个问题的关键所在。 - Mooing Duck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接