Как использовать значения класса 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::array
s отличаются (что приводит к различным типам).
пример:
если вы просто добавить новый 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);
};
};
};
единственное требование для того, чтобы сделать все это работать, чтобы вызвать