C++ Enum Class Iterator

目的: 解決巡迴 KEY 值,需要冗長的轉型

每次要 iterate 強型別的 enum class,都要加上很冗長的轉型符號,讓他可以轉成對應的 整數型態,再去遞增到下一個對應的 enum 值。使用時還要再轉型回來使用。

enum class Animal{Cat, Dog, Cow, Bird};
std::string as_str(Animal animal);

int main()
{
    for(auto key = (std::underlying_type<Animal>::type)Animal::Cat;
        key != (std::underlying_type<Animal>::type)Animal::Bird;
        key++)
    {     
        std::cout << as_str((Animal)key) << std::endl;}
    );
}

Enum

在這裡寫了一個可以 iterate enum class 的工具: Enum,可以 loop 到每一個 key。

版本一: 使用時,指定 enum 的頭尾

enum class Animal{Cat, Dog, Cow, Bird};
std::string as_str(Animal animal);

int main()
{   
    Enum<Animal, Animal::Cat, Animal::Bird> animals;
    for(const auto& animal: animals)
        std::cout << as_str(animal) << std::endl;
}

版本二: 把 enum 的頭尾,定義在 enum

enum class Drink{
    Cola, 
    AppleSoda, 
    BlackTea, 
    GreenTea, 
    First = Cola, 
    Last = GreenTea
};
std::string as_str(Drink drink);

int main()
{
    Enum<Drink> drinks;
    for(const auto& drink: drinks)
        std::cout << as_str(drink) << std::endl;
}

使用限制: 必須連號

目前的 iterator 都是加 1,所以在這裡的 case ,當 Apple + 1 後,就會掉入 Apple 與 Banana 的中間,於是就形成了一個不存在的 key。

enum class Fruit{Apple, Banana=2, Grape, Guava, First = Apple, Last = Guava};
std::string as_str(Fruit fruit)
{
    if(fruit == Fruit::Apple) return "Apple";
    if(fruit == Fruit::Banana) return "Banana";
    if(fruit == Fruit::Grape) return "Grape";
    if(fruit == Fruit::Guava) return "Guava";
    throw std::invalid_argument("Invalid Fruit Key");
}

int main()
{
    for(const auto& fruit: Enum<Fruit>())
        std::cout << as_str(fruit) << std::endl; // exception 在印完 Apple 後,被丟出。
}

實作

EnumIterator

  • 提供了 operator++, operator*, 與 operator!= 來支援 Iterator 最基本的動作。
  • 一些 XXX_type type define 也是 STL algorithm 的需求,至少在 VC2012 上需要。
  • operator++ 目前只 support 加 1,就也是為什麼需要 enum 要連號。

本來想實作可以跳到下一個存在 key 的功能,可是越做越複雜,變得更難用了。後來覺得應該讓使用者知道,要讓 enum class 可以巡迴,就盡量做成簡單的連號,其實也還可以接受。

template <typename EnumT>
class EnumIterator
{
public:
    typedef typename std::underlying_type<EnumT>::type UnderlyingT; 

    // for STL::algorithm requirement
    typedef forward_iterator_tag iterator_category; 
    typedef EnumT value_type;
    typedef std::size_t difference_type;
    typedef value_type& reference;
    typedef value_type* pointer;

    EnumIterator(EnumT e):m_v((UnderlyingT)e){}
    EnumIterator(EnumT e, int offset):m_v((UnderlyingT)e + offset){}
    EnumIterator(UnderlyingT v):m_v(v){}

    UnderlyingT operator++(int)
    {
        return m_v++;
    }

    void operator++()
    {
        ++m_v;
    }

    EnumT operator*()
    {
        return (EnumT)m_v;
    }

    friend bool operator == (EnumIterator<EnumT> lhs_iter, EnumIterator<EnumT> rhs_iter)
    {
        return lhs_iter.m_v == rhs_iter.m_v;
    }

private:
    UnderlyingT m_v;
};

template <typename EnumT>
bool operator != (EnumIterator<EnumT> lhs_iter, EnumIterator<EnumT> rhs_iter)
{
    return !(lhs_iter == rhs_iter);
}

Enum: 包裝 enum

  • 有了 beginend 就可以 support for loop。

  • 同時也提供抽換開頭值與尾值,預設選的 First 與 Last,是為了提版本二的內建頭尾 enum,在使用上時,可以比較輕便。

  • 在 end 中,給與 iterator ctor 一個額外的 1 是要往前 offset 1 的值,來表達最後一個元素的下一個位置。

template<
    typename EnumT, 
    EnumT first_value = EnumT::First, 
    EnumT last_value = EnumT::Last>
class Enum
{public:    
    EnumIterator<EnumT> begin() const
    {
        return EnumIterator<EnumT>(first_value);
    }

    EnumIterator<EnumT> end() const
    {
        return EnumIterator<EnumT>(last_value, 1);
    }
};

code: https://github.com/ot32em/CppStudyGroup/blob/master/EnumIterator.hpp

ref: http://stackoverflow.com/questions/261963/how-can-i-iterate-over-an-enum
ref: http://stackoverflow.com/questions/8498300/allow-for-range-based-for-with-enum-classes

p.s.

  • enum classstd::underlying_type<EnumT>::type 之間的轉型是 static_cast

  • enum class 而不用 enum 是因為兩個原因:

    1. enum 的變數名稱會與同 namespace 下的另一個 enum 撞名。
      ex
      enum Cat{Black} 
      enum Car{Black} // 就會GG
      

    2. enum 會自動轉型成 int,自動轉型在一些場合容易發生錯誤,比較不那麼 typesafe。
      ex
      enum Cat{Black}; 
      void hi(int i); 
      hi(Black); // 不會報錯。
      
comments powered by Disqus