【问题标题】:Enumerate over an enum in C++枚举 C++ 中的枚举
【发布时间】:2010-11-26 07:36:33
【问题描述】:

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

示例用例:

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

可能的重复:

【问题讨论】:

  • 你想如何选择调用函数?你能发布一些伪代码你想怎么做?它可以帮助我们为您提供帮助。
  • 运行时查看stackoverflow.com/questions/1292426/…。 (如果不是编译时,您的问题将与它完全相同。)

标签: c++ enums metaprogramming enumeration


【解决方案1】:

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

让我们用一些&lt;algorithm&gt; 模板进行测试

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

【讨论】:

    【解决方案2】:

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

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

      【讨论】:

        【解决方案4】:

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

        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)... 列表,这很麻烦。

        【讨论】:

          【解决方案5】:

          没有

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

          【讨论】:

            【解决方案6】:

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

            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 以在以后将其更改为更好的内容:-)。

            【讨论】:

            • for 和嵌套的switch 完全没用/毫无意义,因为无论如何您都使用了每个连续的值。两者都省略,直接执行foo stuff,后跟bar stuff等。
            • 这仅在您运行一系列测试的测试程序中有用。开始/结束条件将是变量,因此如果您只想运行一个测试,则设置 start = end: for (int i = start; i
            • 一个for循环来命中switch语句的每个case?请。这是 Stackoverflow,而不是 thedailywtf.com。这只是一种不必要的复杂方式来做“foo stuff”,然后是“bar stuff”。这不需要任何类型的流量控制。
            • @Konrad 和 Alan:我认为这样做的目的是确保您命中每个枚举。如果您在默认语句中崩溃或输出警告或其他内容,您至少会在运行时认识到您在添加新枚举后忘记包含新案例。我真的不确定 OP 的应用程序是什么,所以我只是记下了我认为最简单的方法来完成所要求的操作。似乎原始问题已更新,这不再有意义,我误解了原始问题。但是……这比单独调用每个方法要好一些。
            【解决方案7】:

            我通常这样做:

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

            【讨论】:

              【解决方案8】:

              您可以使用 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 的第 1 章“C++ 模板元编程”,以获得对这种技术的良好处理。与建议的运行时技术相比,这样做的优势在于,当您优化此代码时,它可以内联静态数据,大致相当于:

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

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

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

              【讨论】:

                【解决方案9】:

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

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

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

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-03-22
                  • 2015-08-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多