在 C++ 中枚举枚举

问题描述 投票:0回答:9

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

示例用例:

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

可能的重复:

c++ enums metaprogramming enumeration
9个回答
60
投票

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

让我们用一些

<algorithm>
模板进行测试:

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

42
投票

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

3
投票

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


2
投票

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

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

枚举.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:

#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)
... 列表,这很混乱。


1
投票

没有

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


1
投票

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

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 以便稍后将其更改为更好的东西:-)。


1
投票

我通常这样做:

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

0
投票

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

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

如果我们有一个定义 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};
//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)
   }
}

枚举通常也与 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;

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


0
投票

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

Inline

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

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

© www.soinside.com 2019 - 2024. All rights reserved.