我只是注意到不能在枚举上使用标准数学运算符,如++或+=。

那么,在c++枚举中遍历所有值的最佳方法是什么?


当前回答

这些解决方案太复杂了,我喜欢这样:

enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};

const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };

for (NodePosition pos : NodePositionVector) {
...
}

其他回答

这里有一些非常易读且易于理解的方法,适用于弱类型的C和c++常规枚举,以及强类型的c++枚举类。

我建议使用-Wall -Wextra -Werror编译下面所有的例子。这为您提供了额外的安全性,如果您忘记在开关情况下覆盖任何枚举值,编译器将抛出编译时错误!这迫使您保持枚举定义和开关用例同步,这是代码的额外安全措施。只要你:

覆盖开关案例中的所有枚举值,和 没有默认开关大小写。 使用-Wall -Wextra -Werror旗帜构建。

我建议您遵循所有这3点,因为这是一个很好的实践,可以创建更好的代码。

1. 对于标准的弱类型C或c++ enum:

C定义(这也适用于c++):

typedef enum my_error_type_e 
{
    MY_ERROR_TYPE_SOMETHING_1 = 0,
    MY_ERROR_TYPE_SOMETHING_2,
    MY_ERROR_TYPE_SOMETHING_3,
    MY_ERROR_TYPE_SOMETHING_4,
    MY_ERROR_TYPE_SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    MY_ERROR_TYPE_count,
    // helpers for iterating over the enum
    MY_ERROR_TYPE_begin = 0,
    MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
} my_error_type_t;

c++定义:

enum my_error_type_t 
{
    MY_ERROR_TYPE_SOMETHING_1 = 0,
    MY_ERROR_TYPE_SOMETHING_2,
    MY_ERROR_TYPE_SOMETHING_3,
    MY_ERROR_TYPE_SOMETHING_4,
    MY_ERROR_TYPE_SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    MY_ERROR_TYPE_count,
    // helpers for iterating over the enum
    MY_ERROR_TYPE_begin = 0,
    MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
};

C或c++对弱类型枚举的迭代:

注意:通过my_error_type++递增枚举是不允许的——甚至在c风格的enum上也不允许,所以我们必须这样做:my_error_type = (my_error_type_t)(my_error_type + 1)。注意,my_error_type+ 1是允许的,然而,因为这个弱enum在这里会自动隐式强制转换为int类型,这样就可以不手动强制转换为int类型:my_error_type = (my_error_type_t)((int)my_error_type + 1)。

for (my_error_type_t my_error_type = MY_ERROR_TYPE_begin; 
        my_error_type < MY_ERROR_TYPE_end;
        my_error_type = (my_error_type_t)(my_error_type + 1)) 
{
    switch (my_error_type) 
    {
        case MY_ERROR_TYPE_SOMETHING_1:
            break;
        case MY_ERROR_TYPE_SOMETHING_2:
            break;
        case MY_ERROR_TYPE_SOMETHING_3:
            break;
        case MY_ERROR_TYPE_SOMETHING_4:
            break;
        case MY_ERROR_TYPE_SOMETHING_5:
            break;
        case MY_ERROR_TYPE_count:
            // This case will never be reached.
            break;
    }
}

2. 对于限定作用域的强类型c++枚举类:

c++定义:

enum class my_error_type_t
{
    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,
};

这个强类型枚举的c++迭代:

注意,强制递增枚举类变量需要额外的(size_t)强制转换(或者(int)也可以接受)!这里我还选择使用c++风格的static_cast<my_error_type_t>强制转换,但是C风格的(my_error_type_t)强制转换(如上所示)也可以。

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

Also notice the scoping. In the C++ strongly-typed enum class I used my_error_type_t:: to access each scoped enum class member. But, in the C-style weakly-typed regular enum, very similar scoping can be achieved, as I demonstrated, simply be prefixing each enum member name with MY_ERROR_TYPE_. So, the fact that the C++ strongly-typed enum class adds scoping doesn't really add much value--it's really just a personal preference in that regard. And the fact that the C++ strongly-typed enum class has extra type-safety also has pros and cons. It may help you in some cases but it definitely makes incrementing the enum and iterating over it a pain-in-the-butt, which, honestly, means it is doing its job. By making it harder to increment the scoped enum class variable as though it was an integer, the C++ strongly-typed enum class is doing exactly what it was designed to do. Whether or not you want that behavior is up to you. Personally, I frequently do not want that behavior, and so it is not uncommon for me to prefer to use C-style enums even in C++.

参见:

[我的回答]在c++11中有一种方法通过索引初始化一个向量吗? [我的问答]在c++中迭代枚举类的常用方法是什么? 我对c++中枚举类(强类型enum)和常规enum(弱类型enum)之间的一些差异的回答:如何自动将强类型enum转换为int? 我的一些关于-Wall -Wextra -Werror和其他构建选项的个人笔记,来自我的eRCaGuy_hello_world回购。

这些解决方案太复杂了,我喜欢这样:

enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};

const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };

for (NodePosition pos : NodePositionVector) {
...
}

扩展@Eponymous的回答:它很棒,但没有提供通用语法。这是我想到的:

// Common/EnumTools.h
#pragma once

#include <array>

namespace Common {

// Here we forward-declare metafunction for mapping enums to their values.
// Since C++<23 doesn't have reflection, you have to populate it yourself :-(
// Usage: After declaring enum class E, add this overload in the namespace of E:
// inline constexpr auto allValuesArray(const E&, Commob::EnumAllValuesTag) { return std::array{E::foo, E::bar}; }
// Then `AllValues<NS::E>` will call `allValuesArray(NS::E{}, EnumAllValuesTag)` which will resolve
// by ADL.
// Just be sure to keep it sync'd with your enum!

// Here's what you want to use in, e.g., loops: "for (auto val : Common::AllValues<MyEnum>) {"

struct EnumAllValuesTag {}; // So your allValuesArray function is clearly associated with this header.

template <typename Enum>
static inline constexpr auto AllValues = allValuesArray(Enum{}, EnumAllValuesTag{});
// ^ Just "constexpr auto" or "constexpr std::array<Enum, allValuesArray(Enum{}, EnumAllValuesTag{}).size()>" didn't work on all compilers I'm using, but this did.

} // namespace Common

然后在你的命名空间:

#include "Common/EnumTools.h"

namespace MyNamespace {

enum class MyEnum {
    foo,
    bar = 4,
    baz = 42,
};

// Making this not have to be in the `Common` namespace took some thinking,
// but is a critical feature since otherwise there's no hope in keeping it sync'd with the enum.
inline constexpr auto allValuesArray(const MyEnum&, Common::EnumAllValuesTag) {
    return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz };
}

} // namespace MyNamespace

然后在任何需要使用它的地方:

for (const auto& e : Common::AllValues<MyNamespace::MyEnum>) { ... }

所以即使你有typeded:

namespace YourNS {
using E = MyNamespace::MyEnum;
} // namespace YourNS

for (const auto& e : Common::AllValues<YourNS::E>) { ... }

我想不出比这更好的了,除了每个人都想要的实际语言功能。

未来工作:

您应该能够添加一个constexpr函数(以及一个元函数)来过滤Common::AllValues<E>,从而为枚举具有重复数值的情况提供一个Common::AllDistinctValues<E>,例如enum {foo = 0, bar = 0};。 我打赌有一种方法可以使用编译器的switch- coverage -all-enum-values来编写allValuesArray,这样如果枚举添加了一个值,它就会出错。

typedef enum{
    first = 2,
    second = 6,
    third = 17
}MyEnum;

static const int enumItems[] = {
    first,
    second,
    third
}

static const int EnumLength = sizeof(enumItems) / sizeof(int);

for(int i = 0; i < EnumLength; i++){
    //Do something with enumItems[i]
}

在Bjarne Stroustrup的c++编程语言书中,你可以读到他建议为特定的枚举重载操作符++。枚举是用户定义的类型,语言中存在针对这些特定情况的重载运算符。

你将能够编写以下代码:

#include <iostream>
enum class Colors{red, green, blue};
Colors& operator++(Colors &c, int)
{
     switch(c)
     {
           case Colors::red:
               return c=Colors::green;
           case Colors::green:
               return c=Colors::blue;
           case Colors::blue:
               return c=Colors::red; // managing overflow
           default:
               throw std::exception(); // or do anything else to manage the error...
     }
}

int main()
{
    Colors c = Colors::red;
    // casting in int just for convenience of output. 
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    std::cout << (int)c++ << std::endl;
    return 0;
}

测试代码:http://cpp.sh/357gb

注意,我使用的是枚举类。Code也可以很好地使用enum。但我更喜欢枚举类,因为它们是强类型的,可以防止我们在编译时犯错误。