Как использовать значения класса enum как часть for-loop?

Я пытаюсь создать колоду карт, повторяя перечисления Suit и Rank (Я знаю, что нет отличного способа перебирать перечисления, но я не вижу альтернативы). Я сделал это, добавив перечислитель 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);
        };
    };
};

однако, когда я пытаюсь зациклить перечисление, компилятору не нравится, что я пытаюсь увеличить suit++ и rank++ В for-loop, заявив:

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

каков наилучший способ создания колоды карт, не отбрасывая полезные структуры данных перечисления?

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

вы могли бы бросить свой suit и rank переменные к 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);
        };
    };
};

вы не можете использовать этот 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'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 в этом вопросе, но изменил его: как эмулировать инициализацию массива C " int arr[] = { e1, e2, e3, ... } "поведение с 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::arrays отличаются (что приводит к различным типам).

пример:

если вы просто добавить новый 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 в объявлении массива.


Я тоже хочу поделиться своим подходом, основанным на предыдущий ответ мой создает map<enum,string> используя функции C++11 и C++14, код приведен ниже:

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

единственное требование для того, чтобы сделать все это работать, чтобы вызвать