枚举 C++ 中的枚举

发布于 2024-08-04 06:49:30 字数 491 浏览 2 评论 0原文

在 C++ 中,是否可以枚举枚举(运行时或编译时(首选))并为每次迭代调用函数/生成代码?

示例用例:

enum abc
{    
    start
    a,
    b,
    c,
    end
}    
for each (__enum__member__ in abc)
{    
    function_call(__enum__member__);    
}

看似重复的内容:

In C++, Is it possible to enumerate over an enum (either runtime or compile time (preferred)) and call functions/generate code for each iteration?

Sample use case:

enum abc
{    
    start
    a,
    b,
    c,
    end
}    
for each (__enum__member__ in abc)
{    
    function_call(__enum__member__);    
}

Plausible duplicates:

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

柠檬心 2024-08-11 06:49:30

要添加到 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;
}

让我们用一些 template:

void print(Color c) {
  std::cout << c << std::endl;
}

int main() {
  std::for_each(Color_Begin, Color_End, &print);
}

现在,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++);
}

下面是一个实现。

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

To add to StackedCrooked's answer, you can overload operator++, operator-- and operator* and have iterator-like functionality.

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:

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.

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

An 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);
}
霊感 2024-08-11 06:49:30

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

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:

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));
    }
}
花想c 2024-08-11 06:49:30

如果没有一点体力劳动,这两者都是不可能的。如果您愿意深入研究该领域,很多工作都可以通过宏来完成。

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.

剩一世无双 2024-08-11 06:49:30

扩展一下Konrad所说的,这种情况下可能的一种习语“为每次迭代生成代码”的方法是使用包含文件来表示枚举:

File 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:

// The 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"
}

File main.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();
}

因此,要添加一个新元素“qux”,将其添加到文件 mystuff.h 中并编写 do_qux 函数。您不必触摸调度代码。

当然,如果枚举中的值需要是特定的非连续整数,那么您最终会分别维护枚举定义和 ENUM_ELEMENT(foo)... 列表,这很混乱。

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:

File 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:

// The 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"
}

File main.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 file mystuff.h and write the do_qux function. You don't have to touch the dispatch code.

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.

冷了相思 2024-08-11 06:49:30

但是,您可以定义自己的类,通过迭代实现类似枚举的功能。您可能还记得 Java 1.5 之前的一个技巧,称为“类型安全枚举设计模式”。您可以执行 C++ 等效操作。

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.

蓝眼睛不忧郁 2024-08-11 06:49:30

我通常这样做:

enum abc
{    
    abc_begin,
    a = abc_begin,
    b,
    c,
    abc_end
};

void foo()
{
    for( auto&& r : range(abc_begin,abc_end) )
    {
        cout << r;
    }
}

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

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:

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); }
山川志 2024-08-11 06:49:30

这对我来说似乎很老套,但它可能适合您的目的:

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

如果您需要特殊的起始值,您可以将其设为常量并将其设置在枚举中。我没有检查它是否可以编译,但它应该接近那里:-)。

我认为这种方法对于您的用例来说可能是一个很好的平衡。如果您不需要对一堆不同的枚举类型执行此操作并且您不想处理预处理器的内容,请使用它。只要确保您评论并可能添加一个 TODO 以便稍后将其更改为更好的东西:-)。

This seems hacky to me, but it 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 :-).

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 :-).

三岁铭 2024-08-11 06:49:30

您可以使用 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;
}

该程序的输出为:

0
1
2

请参阅 Abrahams、Gurtovoy 的“C++ 模板元编程”的第 1 章,了解对此技术的良好处理。与建议的运行时技术相比,这样做的优点是,当您优化此代码时,它可以内联静态数据,大致相当于:

function_call(a);
function_call(b);
function_call(c);

内联 function_call 以获得编译器的更多帮助。

对其他枚举迭代技术的同样的批评也适用于此。仅当您的枚举从头到尾连续递增时,此技术才有效。

You can perform some of the proposed runtime techniques statically with 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 Chapter 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:

function_call(a);
function_call(b);
function_call(c);

Inline function_call for even more help from the compiler.

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.

深陷 2024-08-11 06:49:30

我喜欢模板,但我会记下这一点以供我将来/其他人使用,这样我们就不会迷失于上述任何内容。

为了以已知的有序方式比较事物,枚举很方便。为了提高整数值的可读性,它们通常被硬编码到函数中。有点类似于预处理器定义,不同之处在于它们不被文字替换,而是在运行时保留和访问。

如果我们有一个定义 HTML 错误代码的枚举,并且我们知道 500 秒内的错误代码是服务器错误,那么阅读以下内容可能会更好:

enum HtmlCodes {CONTINUE_CODE=100,
                CLIENT_ERROR=400,
                SERVER_ERROR=500,
                NON_STANDARD=600};

if(errorCode >= SERVER_ERROR && errorCode < NON_STANDARD)

关键

if(errorCode >= 500 && errorCode < 600)

部分是这样的,它们与数组类似!但它们用于转换整数值

简短示例:

enum Suit {Diamonds, Hearts, Clubs, Spades};

// Do something with values in the enum past "Hearts" in this case
for(int i=0; i<4; i++) {
   // It could also use i or Hearts, because the enum
   // will turn these both back into an int
   if((Suit)(i) > 1)
   {
      // Whatever we'd like to do with (Suit)(i)
   }
}

通常,枚举还与 char* 数组或字符串数​​组一起使用,以便您可以打印一些带有关联值的消息。通常它们只是枚举中具有相同值集的数组,如下所示:

char* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"};
// It is getting a little redundant
cout << Suits[Clubs] << endl;

// We might want to add this to the above
//cout << Suits[(Suit)(i)] << endl;

当然,创建一个处理枚举迭代的通用类(如上面的答案)会更好。

I love templating, but I'm going to make note of this for my future/other peoples' 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 they are kept and accessed at 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:

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 they are used to cast integer values.

Short example:

enum Suit {Diamonds, Hearts, Clubs, Spades};

// Do something with values in the enum past "Hearts" in this case
for(int i=0; i<4; i++) {
   // It could also use i or Hearts, because the enum
   // will turn 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* Suits[4] = {"Diamonds", "Hearts", "Clubs", "Spades"};
// It is 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文