如何使用枚举类值作为 for 循环的一部分
How to use enum class values as part of 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);
};
};
};
但是,当我尝试遍历枚举时,编译器不喜欢我尝试在 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});
您可以将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);
};
};
};
使用 C++11,您可以使用基于范围的 for 循环。您所需要的只是定义一个带有operator++
、operator!=
和operator*
的迭代器类,并将begin
或end
定义为成员函数或自由函数。
下面是使用包含Iterator
类和begin
或end
成员函数的 EnumRange
类的示例。该类假定 T
是具有从 0 开始并在 MAX
结束的连续值的enum class
。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
类存储单个整数,并且自动支持对枚举的任何更改。此外,由于我们已经定义了将整数转换为枚举类型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 示例。
- 如何在C++中从两个单独的for循环中添加两个数组
- 为什么我的for循环不能正确获取argv
- 在基于范围的for循环中使用结构化绑定声明
- 通过for循环使用用户输入填充列表
- 使用for循环检查数组中的重复项
- 在for循环中使用auto vs decltype(vec.size())来处理字符串的向量
- 为什么 const std::p air<K,V>& 在 std::map 上基于范围的 for 循环不起作用?
- 正在使用for循环创建QScatterSerie
- Python中的for循环与C++有何不同
- 在更改for循环的第三部分后,未使用for循环结果
- 在 for 循环中查找问题时遇到困难
- 嵌套for循环C++的问题(初学者)
- 如何用for循环在c++中生成单词三角形
- 如何在for循环中包含两个索引值的测试条件
- 带有多个独立参数的C++For循环
- 为什么我的程序在for循环中k=0时返回垃圾值
- 如何通过替换顺序代码的while循环来添加OpenMP for循环
- C++-For循环未执行
- 基于范围的 for 循环:迭代使用一个元素扩展的向量
- C++ 无法在字符数组中使用 for 循环打印字母模式