【问题标题】:What are commonly-used ways to iterate over an enum class in C++?在 C++ 中迭代枚举类的常用方法是什么?
【发布时间】:2021-10-29 01:23:00
【问题描述】:

不幸的是,我发现所有用于迭代常规 enums 的标准技术都不适用于 enum classes,因为枚举类不会隐式转换为整数。

不是How can I iterate over an enum? 的重复项,因为我问的是enum class(即:强类型枚举),而他们问的是常规的enum(即: 弱类型枚举)。

【问题讨论】:

  • 一种非常常见的方法是使用一个包含所有枚举值的静态表,然后对其进行迭代。它比使用整数类型和枚举类之间的显式转换以及在整数范围上迭代要少脏。当然,如果您在枚举中添加/删除/重命名值,它会以存储为代价并且需要额外的精力来维护表。
  • @paddy,你能举个例子吗?

标签: c++ enums enumeration enum-class


【解决方案1】:

另一种选择是使用 C++20 范围来组成 enum 范围:

constexpr inline auto enum_range = [](auto front, auto back) {
  return std::views::iota(std::to_underlying(front), std::to_underlying(back) + 1) 
       | std::views::transform([](auto e) { return decltype(front)(e); }); 
};

然后你可以像这样迭代enum

enum class color { red, yellow, green, blue };
for (const auto e : enum_range(color::red, color::blue))
  // ...

demo.

【讨论】:

  • 太棒了!这是否适用于范围枚举的所有允许的基础类型(根据标准?)?
  • @Secundi。底层类型必须是整数类型,所以是的。
【解决方案2】:

这是我能想到的最易读和最简单的方法,但我愿意接受其他人的示例解决方案。

我发现这种方法易于使用,类似于我的 C 方法(使其更便携和更易识别),并且适用于 C++。它使用-Wall -Wextra -Werror 编译器构建选项进行编译。

enum class MyErrorType 
{
    SOMETHING_1 = 0,
    SOMETHING_2,
    SOMETHING_3,
    SOMETHING_4,
    SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    _COUNT,
    // helpers for iterating over the enum
    begin = 0,
    end = _COUNT,
};

for (MyErrorType myErrorType = (MyErrorType)0; 
        myErrorType < MyErrorType::_COUNT;
        myErrorType = static_cast<MyErrorType>((size_t)myErrorType + 1)) 
{
    switch (myErrorType) 
    {
        case MyErrorType::SOMETHING_1:
            break;
        case MyErrorType::SOMETHING_2:
            break;
        case MyErrorType::SOMETHING_3:
            break;
        case MyErrorType::SOMETHING_4:
            break;
        case MyErrorType::SOMETHING_5:
            break;
        case MyErrorType::_COUNT:
            // This case will never be reached. It is included only so that when
            // compiling with `-Wall -Wextra -Werror` build flags you get the
            // added bonus of covering all switch cases (withOUT unnecessarily
            // relying on a `default` case which would break this feature!), so
            // if you ever add a new element to the enum class but forget to
            // add it here to the switch case the compiler will THROW AN ERROR.
            // This is an added safety benefit to force you to keep your enum
            // and the switch statement in-sync! It's a technique commonly used
            // in C as well.
            break;
    }
}

请阅读我的 cmets 以了解上述 MyErrorType::_COUNT 案例!如果您使用编译器的 -Wall -Wextra -Werror 编译器选项,但不要在 switch 语句中包含这种情况(因为这些构建选项要求您在所有 switch 语句中涵盖所有枚举情况!),编译器将抛出以下错误并停止!这是一个很好的安全功能,可确保您保持枚举定义和所有 switch case 同步,处理所有 switch 语句中的所有可能枚举。这是 LLVM 的 clang 编译器(gcc 的替代品)引发的编译器错误:

../my_file.cpp:11:16: error: enumeration value ‘_COUNT’ not handled in switch [-Werror=switch]
   11 |         switch (myErrorType) {
      |                ^

为了清楚起见,对上述代码的另一个微小改进是将beginend 元素添加到您的枚举中,如下所示:

enum class MyErrorType 
{
    SOMETHING_1 = 0,
    SOMETHING_2,
    SOMETHING_3,
    SOMETHING_4,
    SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    _COUNT,
    // helpers for iterating over the enum
    begin = 0,
    end = _COUNT,
};

...这样您就可以按如下方式遍历枚举。 for 循环的递增部分对于所有必需的转换仍然有点麻烦,但初始状态和结束条件检查现在至少更清晰了,因为它们使用 MyErrorType::beginMyErrorType::end

for (MyErrorType myErrorType = MyErrorType::begin; 
        myErrorType < MyErrorType::end;
        myErrorType = static_cast<MyErrorType>((size_t)myErrorType + 1)) 
{
    switch (myErrorType) 
    {
        case MyErrorType::SOMETHING_1:
            break;
        case MyErrorType::SOMETHING_2:
            break;
        case MyErrorType::SOMETHING_3:
            break;
        case MyErrorType::SOMETHING_4:
            break;
        case MyErrorType::SOMETHING_5:
            break;
        case MyErrorType::_COUNT:
            // This case will never be reached.
            break;
    }
}

相关:

  1. 迭代enums(相对于enum classes)的常用技术:How can I iterate over an enum?
    1. [我的回答]How can I iterate over an enum?
  2. 我对 C++ 中 enum classes(强类型枚举)和常规 enums(弱类型枚举)之间的一些差异的回答: How to automatically convert strongly typed enum into int?
  3. Some of my personal notes on the -Wall -Wextra -Werror and other build options,来自我的 eRCaGuy_hello_world 存储库。
  4. Incrementation and decrementation of “enum class”

其他关键字:在 C 或 C++ 中迭代枚举或枚举类的常用方法;在 C++ 中迭代枚举类的最佳方法;枚举类 C++ 迭代; c++遍历枚举类

【讨论】:

  • 如果这是您的首选风格,您可能需要通过为枚举类定义“开始”和“结束”值(类似于 C++ 迭代器)和重载 operator++ 来考虑一些小的调整。它使循环更清洁。示例here.
  • @paddy,好方法in your example。但是,如果我有 10 个不同的 enum classes,我会发现这种方法很难维护所有不同的增量运算符重载函数。有没有办法创建一个单个宏或模板,它涵盖了所有 enum class 类型来增加任何这些类型的枚举类变量?
  • 这个想法的一个简单扩展将类似于this,只需创建一个模板类来包装枚举类型以进行迭代。我个人不喜欢宏,但你可以玩弄它们并使它们变得更好。我更喜欢查看实际的枚举类定义,而不是将语法隐藏在宏中。
猜你喜欢
  • 2010-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-10
  • 2016-05-19
相关资源
最近更新 更多