如何使用枚举类值作为 for 循环的一部分

How to use enum class values as part of for-loop?

本文关键字:for 循环 一部分 何使用 枚举      更新时间:2023-10-16

我正在尝试通过迭代枚举SuitRank来创建一副牌(我知道没有很好的方法来迭代枚举,但我看不到替代方案(。我通过在每个枚举的末尾添加一个枚举器enum_count来实现这一点,枚举的值旨在表示枚举的长度和结尾。

#include <vector>
using namespace std;
enum class Suit: int {clubs, diamonds, hearts, spades, enum_count};
enum class Rank: int {one, two, three, four, five, six, seven, eight,
                nine, ten, jack, queen, king, ace, enum_count};
struct Card {
    Suit suit;
    Rank rank;
};
class Deck{
    vector<Card> cards{};
    public:
        Deck();
};
Deck::Deck() {
    // ERROR ON THE BELOW LINE
    for (Suit suit = Suit::clubs; suit < Suit::enum_count; suit++) {
        for (Rank rank = Rank::one; rank < Rank::enum_count; rank++) {
            Card created_card;
            created_card.suit = suit;
            created_card.rank = rank;
            cards.push_back(created_card);
        };
    };
};

但是,当我尝试遍历枚举时,编译器不喜欢我尝试在 for 循环中增加suit++rank++,指出:

card.cpp|24|error: no ‘operator++(int)’ declared for postfix ‘++’ [-fpermissive]|
card.cpp|25|error: no ‘operator++(int)’ declared for postfix ‘++’ [-fpermissive]|

在不丢弃有用的枚举数据结构的情况下创建一副纸牌的最佳方法是什么?

我建议做一些不同的事情。创建一个 Suit 向量和一个 to Rank 的向量,并使用 STL 的强大功能遍历它们

const std::vector<Suit> v_suit {Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades};
const std::vector<Rank> v_rank {Rank::one, Rank::two, Rank::three, Rank::four, Rank::five, 
                          Rank::six, Rank::seven, Rank::eight, Rank::nine, Rank::ten, Rank::jack, 
                          Rank::queen, Rank::king, Rank::ace};

是的,您必须键入它们两次,但这允许您使用您想要的任何值(即非连续(,而不是使用像enum_count这样的尴尬的东西(你想要什么卡?给我一个菱形enum_count!!(,不需要铸造,使用提供给std::vector的迭代器。

要使用它们:

for(const auto & s : v_suit)
    for (const auto & r : v_rank)
        cards.push_back({s,r});

您可以将suitrank变量强制转换为int&并增加它们。

    for (Suit suit = Suit::clubs; suit < Suit::enum_count; ((int&)suit)++) {
        for (Rank rank = Rank::one; rank < Rank::enum_count; ((int&)rank)++) {

但是,这可能会导致一些问题,例如当您为枚举条目分配值时。


您还可以创建一个小函数,使用正确的类型为您执行此操作:

template <typename T>
T& increment(T& value)
{
    static_assert(std::is_integral<std::underlying_type_t<T>>::value, "Can't increment value");
    ((std::underlying_type_t<T>&)value)++;
    return value;
}
Deck::Deck() {
    bool a = std::is_integral<std::underlying_type_t<Suit>>::value;
    // ERROR ON THE BELOW LINE
    for (Suit suit = Suit::clubs; suit < Suit::enum_count; increment(suit)) {
        for (Rank rank = Rank::one; rank < Rank::enum_count; increment(rank)) {
            Card created_card;
            created_card.suit = suit;
            created_card.rank = rank;
            cards.push_back(created_card);
        };
    };
};

使用 C++11,您可以使用基于范围的 for 循环。您所需要的只是定义一个带有operator++operator!=operator*的迭代器类,并将beginend定义为成员函数或自由函数。

下面是使用包含Iterator类和beginend成员函数的 EnumRange 类的示例。该类假定 T 是具有从 0 开始并在 MAX 结束的连续值的enum classMAX声明用于避免向枚举添加无效值,例如 enum_count 。如果enum class没有定义MAX,则代码将不会编译。

template <class T>
struct EnumRange {
  struct Iterator {
    explicit Iterator(int v) : value(v) {}
    void operator++() { ++value; }
    bool operator!=(Iterator rhs) { return value != rhs.value; }
    T operator*() const { return static_cast<T>(value); }
    int value = 0;
  };
  Iterator begin() const { return Iterator(0); }
  Iterator end() const { return Iterator(static_cast<int>(T::MAX) + 1); }
};

这允许您简单地编写:

enum class Suit {clubs, diamonds, hearts, spades, MAX=spades};
enum class Rank {one, two, three, four, five, six, seven, eight,
                 nine, ten, jack, queen, king, ace, MAX=ace};
for(const Suit s : EnumRange<Suit>())
    for (const Rank r : EnumRange<Rank>())
        cards.push_back({s,r});

这种方法的一个优点是,它避免了每次要迭代枚举时定义/分配映射或向量的需要。相反,EnumRange::Iterator 类存储单个整数,并且自动支持对枚举的任何更改。此外,由于我们已经定义了将整数转换为枚举类型T operator*,我们知道基于范围的 for 循环的变量类型是枚举。

总之,结果是易于阅读的表达式for(MyEnum s : EnumRange<MyEnum>()) .

您不能将其与enum class一起使用。您必须使用旧式enum

如果你坚持使用它们。我可以建议你一个不好的解决方案,不要使用(除非你不会告诉任何人我建议的(:

for (Rank rank = Rank::one; 
     static_cast<int>(rank) < static_cast<int>(Rank::enum_count); 
     rank = static_cast<Rank>(static_cast<int>(rank)+1)){
};

回应old_mountain回答的其他答案:

在某些情况下,您可以使用固定数组防止忘记向列表中添加新值。这样做的主要问题是初始值设定项接受的参数少于指定的参数,但您可以解决此问题:

template<typename T, typename...Args>
struct first_type
{
    using type = T;
};
template<typename... Args> 
std::array<typename first_type<Args...>::type, sizeof...(Args)> make_array(Args&&... refs) 
{
    return std::array<typename first_type<Args...>::type, sizeof...(Args)>{ { std::forward<Args>(refs)... } };
}

我在这个问题中找到了make_array的灵感,但对其进行了修改: 如何模拟 C 数组初始化 "int arr[] = { e1, e2, e3, ... }" std::数组的行为?

它的作用是使用第一个参数来找出std::array的类型以及参数的数量以获得实际大小。所以返回类型是 std::array<firstType, numArgs> .

现在像这样声明你的列表:

const std::array<Suit, (size_t)Suit::enum_count> SuitValues = 
    make_array(Suit::clubs, Suit::diamonds, Suit::hearts, Suit::spades);
const std::array<Rank, (size_t)Rank::enum_count> RankValues = 
    make_array(Rank::one, Rank::two, Rank::three, Rank::four, 
               Rank::five, Rank::six, Rank::seven, Rank::eight,
               Rank::nine, Rank::ten, Rank::jack, Rank::queen, 
               Rank::king, Rank::ace);

当您向数组中添加值时,您的enum_count或用作分隔符的任何值都将更改,因此来自make_array的赋值将失败,因为两个std::array的大小不同(这导致不同的类型(。

例:

如果您只是添加一个新Suit,假设hexa

enum class Suit : int { clubs, diamonds, hearts, spades, hexa, enum_count };

编译器将失败,并显示:

cannot convert from 'std::array<T,0x04>' to 'const std::array<Suit,0x05>'

我不得不承认我对这个解决方案不是 100% 满意,因为它需要一个非常丑陋的强制转换才能在数组声明中size_t

我还想分享我的方法,基于我之前使用 C++11 和 C++14 功能创建map<enum,string>的答案,代码如下:

// Shortcut to the map
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;
// Template variable for each enumerated type
template <typename ENUM>
enum_map<ENUM> enum_values{};
// Empty function to end the initialize recursion
void initialize(){}
// Recursive template which initializes the enum map
template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize(tail ...);
}

使用此模板,我们可以通过以下方式更改Deck构造函数:

Deck::Deck() {
    for (const auto &S : enum_values<Suit>) {
        for (const auto &R : enum_values<Rank>) {
            Card created_card;
            created_card.suit = S.first;
            created_card.rank = R.first;
            cards.push_back(created_card);
        };
    };
};

为了使整个事情正常工作,唯一的要求是这样调用initialize函数:

initialize
(
    Suit::clubs,    "Clubs",
    Suit::diamonds, "Diamonds",
    Suit::hearts,   "Hearts",
    Suit::spades,   "Spades",
    Rank::one,   "1",
    Rank::two,   "2",
    Rank::three, "3",
    Rank::four,  "4",
    Rank::five,  "5",
    Rank::six,   "6",
    Rank::seven, "7",
    Rank::eight, "8",
    Rank::nine,  "9",
    Rank::ten,   "10",
    Rank::jack,  "J",
    Rank::queen, "Q",
    Rank::king,  "K",
    Rank::ace,   "A"
);

您可以查看 Live 示例