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

时间:2016-02-10 10:46:03

标签: c++ loops c++11 enums

我试图通过迭代枚举SuitRank来创建一副牌(我知道那里没有很好的方法可以迭代枚举但是我没有&# 39;看到另一种选择)。我通过在每个枚举的末尾添加一个枚举器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]|

创建一副卡片而不丢弃有用的枚举数据结构的最佳方法是什么?

6 个答案:

答案 0 :(得分:5)

我建议做一些与众不同的事情。 创建一个Suit和一个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});

答案 1 :(得分:4)

您可以将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);
        };
    };
};

答案 2 :(得分:3)

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

这里是使用EnumRange类的示例,该类包括Iterator类和beginend成员函数。该类假定T是一个enum class,其连续值从0开始并在MAX结束。 MAX声明用于避免向枚举添加无效值,例如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类存储单个整数,并且自动支持对枚举的任何更改。另外,由于我们已经定义operator*来将整数转换为枚举类型T,所以我们知道基于范围的for循环的变量类型是枚举。

总而言之,结果就是易于理解的表达式for(MyEnum s : EnumRange<MyEnum>())

答案 3 :(得分:2)

您无法在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)){
};

答案 4 :(得分:2)

回复old_mountain's answer的其他答案:

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

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的灵感,但修改了它:How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array?

它的作用是使用第一个参数来找出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>'

我必须承认,我对此解决方案并不十分满意,因为它需要在数组声明中对size_t进行非常丑陋的转换。

答案 5 :(得分:1)

我还希望根据使用C ++ 11和C ++ 14功能创建map<enum,string>的{​​{3}}分享我的方法,代码如下:

// 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"
);

您可以查看 previous answer of mine