在 C++ 中枚举枚举
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1390703/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Enumerate over an enum in C++
提问by jameszhao00
In C++, Is it possible to enumerate over an enum (either runtime or compile time (preferred)) and call functions/generate code for each iteration?
在 C++ 中,是否可以枚举枚举(运行时或编译时(首选))并为每次迭代调用函数/生成代码?
Sample use case:
示例用例:
enum abc
{
start
a,
b,
c,
end
}
for each (__enum__member__ in abc)
{
function_call(__enum__member__);
}
Plausible duplicates:
似是而非的重复:
回答by Johannes Schaub - litb
To add to @StackedCrookedanswer, you can overload operator++
, operator--
and operator*
and have iterator like functionality.
要添加到@StackedCrooked答案,就可以超载operator++
,operator--
并operator*
与具有迭代器一样的功能。
enum Color {
Color_Begin,
Color_Red = Color_Begin,
Color_Orange,
Color_Yellow,
Color_Green,
Color_Blue,
Color_Indigo,
Color_Violet,
Color_End
};
namespace std {
template<>
struct iterator_traits<Color> {
typedef Color value_type;
typedef int difference_type;
typedef Color *pointer;
typedef Color &reference;
typedef std::bidirectional_iterator_tag
iterator_category;
};
}
Color &operator++(Color &c) {
assert(c != Color_End);
c = static_cast<Color>(c + 1);
return c;
}
Color operator++(Color &c, int) {
assert(c != Color_End);
++c;
return static_cast<Color>(c - 1);
}
Color &operator--(Color &c) {
assert(c != Color_Begin);
return c = static_cast<Color>(c - 1);
}
Color operator--(Color &c, int) {
assert(c != Color_Begin);
--c;
return static_cast<Color>(c + 1);
}
Color operator*(Color c) {
assert(c != Color_End);
return c;
}
Let's test with some <algorithm>
template
让我们用一些<algorithm>
模板进行测试
void print(Color c) {
std::cout << c << std::endl;
}
int main() {
std::for_each(Color_Begin, Color_End, &print);
}
Now, Color
is a constant bidirectional iterator. Here is a reusable class i coded while doing it manually above. I noticed it could work for many more enums, so repeating the same code all over again is quite tedious
现在,Color
是一个常量双向迭代器。这是我在上面手动执行时编码的可重用类。我注意到它可以用于更多的枚举,所以一遍又一遍地重复相同的代码是相当乏味的
// Code for testing enum_iterator
// --------------------------------
namespace color_test {
enum Color {
Color_Begin,
Color_Red = Color_Begin,
Color_Orange,
Color_Yellow,
Color_Green,
Color_Blue,
Color_Indigo,
Color_Violet,
Color_End
};
Color begin(enum_identity<Color>) {
return Color_Begin;
}
Color end(enum_identity<Color>) {
return Color_End;
}
}
void print(color_test::Color c) {
std::cout << c << std::endl;
}
int main() {
enum_iterator<color_test::Color> b = color_test::Color_Begin, e;
while(b != e)
print(*b++);
}
Implementation follows.
实施如下。
template<typename T>
struct enum_identity {
typedef T type;
};
namespace details {
void begin();
void end();
}
template<typename Enum>
struct enum_iterator
: std::iterator<std::bidirectional_iterator_tag,
Enum> {
enum_iterator():c(end()) { }
enum_iterator(Enum c):c(c) {
assert(c >= begin() && c <= end());
}
enum_iterator &operator=(Enum c) {
assert(c >= begin() && c <= end());
this->c = c;
return *this;
}
static Enum begin() {
using details::begin; // re-enable ADL
return begin(enum_identity<Enum>());
}
static Enum end() {
using details::end; // re-enable ADL
return end(enum_identity<Enum>());
}
enum_iterator &operator++() {
assert(c != end() && "incrementing past end?");
c = static_cast<Enum>(c + 1);
return *this;
}
enum_iterator operator++(int) {
assert(c != end() && "incrementing past end?");
enum_iterator cpy(*this);
++*this;
return cpy;
}
enum_iterator &operator--() {
assert(c != begin() && "decrementing beyond begin?");
c = static_cast<Enum>(c - 1);
return *this;
}
enum_iterator operator--(int) {
assert(c != begin() && "decrementing beyond begin?");
enum_iterator cpy(*this);
--*this;
return cpy;
}
Enum operator*() {
assert(c != end() && "cannot dereference end iterator");
return c;
}
Enum get_enum() const {
return c;
}
private:
Enum c;
};
template<typename Enum>
bool operator==(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
return e1.get_enum() == e2.get_enum();
}
template<typename Enum>
bool operator!=(enum_iterator<Enum> e1, enum_iterator<Enum> e2) {
return !(e1 == e2);
}
回答by StackedCrooked
C++ currently does not provide enumerator iteration. Despite that, the need sometimes arises for this. A common workaround is to add values that mark the beginning and the ending. For example:
C++ 当前不提供枚举器迭代。尽管如此,有时也需要这样做。一种常见的解决方法是添加标记开始和结束的值。例如:
enum Color
{
Color_Begin,
Color_Red = Color_Begin,
Color_Orange,
Color_Yellow,
Color_Green,
Color_Blue,
Color_Indigo,
Color_Violet,
Color_End
};
void foo(Color c)
{
}
void iterateColors()
{
for (size_t colorIdx = Color_Begin; colorIdx != Color_End; ++colorIdx)
{
foo(static_cast<Color>(colorIdx));
}
}
回答by Konrad Rudolph
Neither is possible without a little manual labour. A lot of the work can be done by macros, if you're willing to delve into that area.
没有一点体力劳动,这两种方法都是不可能的。如果您愿意深入研究该领域,那么很多工作都可以通过宏来完成。
回答by Steve Jessop
Expanding on what Konrad says, one possible idiom in the case of "generate code for each iteration" is to use an included file to represent the enumeration:
扩展 Konrad 所说的内容,在“为每次迭代生成代码”的情况下,一种可能的习惯用法是使用包含的文件来表示枚举:
mystuff.h:
mystuff.h:
#ifndef LAST_ENUM_ELEMENT
#define LAST_ENUM_ELEMENT(ARG) ENUM_ELEMENT(ARG)
#endif
ENUM_ELEMENT(foo)
ENUM_ELEMENT(bar)
LAST_ENUM_ELEMENT(baz)
// not essential, but most likely every "caller" should do it anyway...
#undef LAST_ENUM_ELEMENT
#undef ENUM_ELEMENT
enum.h:
枚举.h:
// include guard goes here (but mystuff.h doesn't have one)
enum element {
#define ENUM_ELEMENT(ARG) ARG,
#define LAST_ENUM_ELEMENT(ARG) ARG
#include "mystuff.h"
}
main.cpp:
主.cpp:
#include "enum.h"
#define ENUM_ELEMENT(ARG) void do_##ARG();
#include "mystuff.h"
element value = getValue();
switch(value) {
#define ENUM_ELEMENT(ARG) case ARG: do_##ARG(); break;
#include "mystuff.h"
default: std::terminate();
}
So, to add a new element "qux", you add it to mystuff.h and write the do_qux
function. You don't have to touch the dispatch code.
因此,要添加新元素“qux”,请将其添加到 mystuff.h 并编写do_qux
函数。您不必触摸调度代码。
Of course if the values in your enum need to be specific non-consecutive integers, then you end up maintaining the enum definition and the ENUM_ELEMENT(foo)
... list separately, which is messy.
当然,如果您的枚举中的值需要是特定的非连续整数,那么您最终需要分别维护枚举定义和ENUM_ELEMENT(foo)
... 列表,这很麻烦。
回答by Tom
This seems hacky to me, but may suit your purposes:
这对我来说似乎很棘手,但可能适合您的目的:
enum Blah {
FOO,
BAR,
NUM_BLAHS
};
// later on
for (int i = 0; i < NUM_BLAHS; ++i) {
switch (i) {
case FOO:
// foo stuff
break;
case BAR:
// bar stuff
break;
default:
// you're missing a case statement
}
}
If you need a special start value, you can make that a constant and set it in your enum. I didn't check if this compiles, but it should be close to being there :-). Hope this helps.
如果您需要一个特殊的起始值,您可以将其设为常量并将其设置在您的枚举中。我没有检查它是否编译,但它应该接近存在:-)。希望这可以帮助。
I think this approach might be a good balance for your use case. Use it if you don't need to do this for a bunch of different enumerated types and you don't want to deal with preprocessor stuff. Just make sure you comment and probably add a TODO to change it at a later date to something better :-).
我认为这种方法可能是您的用例的一个很好的平衡。如果您不需要为一堆不同的枚举类型执行此操作并且您不想处理预处理器的内容,请使用它。只要确保您发表评论并可能添加一个 TODO 以便在以后将其更改为更好的 :-)。
回答by DigitalRoss
No
不
However, you could define your own class that implements enum-like features with iterations. You may recall a trick from the pre 1.5 Java days, called the "type safe enum design pattern". You could do the C++ equivalent.
但是,您可以定义自己的类,通过迭代实现类似枚举的功能。您可能还记得 Java 1.5 之前的一个技巧,称为“类型安全枚举设计模式”。你可以做 C++ 等价物。
回答by sp2danny
I usually do that like this:
我通常这样做:
enum abc
{
abc_begin,
a = abc_begin,
b,
c,
abc_end
};
void foo()
{
for( auto&& r : range(abc_begin,abc_end) )
{
cout << r;
}
}
range
is completely generic, and defined like follows:
range
是完全通用的,定义如下:
template <typename T>
class Range
{
public:
Range( const T& beg, const T& end ) : b(beg), e(end) {}
struct iterator
{
T val;
T operator*() { return val; }
iterator& operator++() { val = (T)( 1+val ); return *this; }
bool operator!=(const iterator& i2) { return val != i2.val; }
};
iterator begin() const { return{b}; }
iterator end() const { return{e}; }
private:
const T& b;
const T& e;
};
template <typename T>
Range<T> range( const T& beg, const T& end ) { return Range<T>(beg,end); }
回答by TheUnknownGeek
Love templating but I'm going to make note of this for my future/other people's usage so we're not lost with any of the above.
喜欢模板,但我会为我的未来/其他人的使用记下这一点,这样我们就不会迷失上述任何内容。
Enums are convenient for the sake of comparing things in a known ordered fashion. They are typically used hard-coded into functions for the sake of readability against integer values. Somewhat similar to preprocessor definitions, with the exception that they are not replaced with literals, but kept and accessed in runtime.
为了以已知的有序方式比较事物,枚举很方便。为了对整数值具有可读性,它们通常被硬编码到函数中。有点类似于预处理器定义,不同之处在于它们没有被替换为文字,而是在运行时保留和访问。
If we had an enum defining html error codes and we knew that error codes in the 500s are server errors, it might be nicer to read something like:
如果我们有一个定义 html 错误代码的枚举,并且我们知道 500 年代的错误代码是服务器错误,那么阅读以下内容可能会更好:
enum HtmlCodes {CONTINUE_CODE=100,CLIENT_ERROR=400,SERVER_ERROR=500,NON_STANDARD=600};
if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD)
than
比
if(errorCode >= 500 && errorCode < 600)
The key part is this, they are similar to arrays! But are used tocastinteger values.
关键是这个,它们类似于数组!但用于投整数值。
Short example:
简短示例:
enum Suit {Diamonds, Hearts, Clubs, Spades};
//does something with values in the enum past "Hearts" in this case
for(int i=0;i<4;i++){
//Could also use i or Hearts, because the enum will turns these both back into an int
if( (Suit)(i) > 1 )
{
//Whatever we'd like to do with (Suit)(i)
}
}
Oftentimes enums are also used with char* arrays or string arrays so that you could print some message with the associated value. Normally they're just arrays with the same set of values in the enum, like so:
通常,枚举也与 char* 数组或字符串数组一起使用,以便您可以打印一些带有关联值的消息。通常,它们只是在枚举中具有相同值集的数组,如下所示:
char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"};
//Getting a little redundant
cout << Suits[Clubs] << endl;
//We might want to add this to the above
//cout << Suits[(Suit)(i)] << endl;
And of course it's even nicer to create a generic class which handles iteration for enums like the answers above.
当然,创建一个通用类来处理枚举的迭代(如上面的答案)会更好。
回答by Ken Smith
You can perform some of the proposed runtime techniques statically with TMP.
您可以使用 TMP 静态执行一些建议的运行时技术。
#include <iostream>
enum abc
{
a,
b,
c,
end
};
void function_call(abc val)
{
std::cout << val << std::endl;
}
template<abc val>
struct iterator_t
{
static void run()
{
function_call(val);
iterator_t<static_cast<abc>(val + 1)>::run();
}
};
template<>
struct iterator_t<end>
{
static void run()
{
}
};
int main()
{
iterator_t<a>::run();
return 0;
}
The output from this program is:
这个程序的输出是:
0
1
2
See Ch 1 of Abrahams, Gurtovoy "C++ Template Metaprogramming" for a good treatment of this technique. The advantage to doing it this way over the proposed runtime techniques is that, when you optimize this code, it can inline the statics and is roughly equivalent to:
请参阅 Abrahams, Gurtovoy 第 1 章“C++ 模板元编程”以了解对这种技术的良好处理。与建议的运行时技术相比,这样做的优势在于,当您优化此代码时,它可以内联静态,大致相当于:
function_call(a);
function_call(b);
function_call(c);
Inline function_call for even more help from the compiler.
内联 function_call 从编译器获得更多帮助。
The same criticisms of other enumeration iteration techniques apply here. This technique only works if your enumeration increments continuously from a through end by ones.
对其他枚举迭代技术的批评同样适用于此。此技术仅在您的枚举从头到尾逐个递增时才有效。