We iterate over enums in C++

We iterate over enums in C++

In this article, I want to share a simple and obvious trick in C++ that, despite its simplicity and obviousness, not everyone knows about.

Suppose you have enum class, and you want to walk through its elements, that is, call a function for each of the values ​​of this enum. This can be useful, well… for example, if the enum’s elements represent parameters (keys to values) that you can get from some entity or service, and you need to process them all at once in turn, for example for serialization or logging in

C# has a method for this task Enum.GetValues(typeof(T)), which returns a collection of all values ​​of this enum. There is no such method in C++, but we can try to implement something similar.

So, let’s take the usual and everyone’s favorite range-based for loop:

for (auto i : some)
{
  ...
}

Cppreference tells us that to use a range-based for loop with any type that is not an array, that type must have begin() and end() methods that return something that supports the ++ operators (to get to the next value ) and * (to get the value), i.e. an iterator.

Based on this, we start creating our template class, in which we can wrap any enum. First of all, let’s start with the iterator:

template <typename T>
class Enum
{
public:
  class Iterator
  {
  public:
    constexpr explicit Iterator(std::underlying_type_t<T> value)
      : m_value(value)
    {}
    constexpr T operator*() const
    {
      return static_cast<T>(m_value);
    }
    constexpr void operator++()
    {
      ++m_value;
    }
    constexpr bool operator!=(Iterator rhs) const
    {
      return m_value != rhs.m_value;
    }
  private:
    std::underlying_type_t<T> m_value;
  };
};

Enum “under the hood” is a numeric value, so we can easily convert it to a numeric type and back (it won’t necessarily be an int, so we use std::underlying_type). The only condition is that the elements in the enum must be consecutive, without gaps.

We figured out the iterator, but how to implement the begin() and end() functions?

To do this, we will need to do a little trickery on our ina. Let’s say you want to create an enumeration with elements One, Two, and Three.

Let’s declare it like this:

enum class MyType
{
  Begin,
  One = Begin,
  Two,
  Three,
  End
};

Now we can easily access the first element of the enumeration via the MyType::Begin alias (which is numerically equivalent to the One element), and check if we’ve reached the end of the enumeration by comparing it to MyType::End.

After that, the code for the begin() and end() methods is placed literally in a couple of lines:

template <typename T>
constexpr typename Enum<T>::Iterator begin(Enum<T>)
{
  return typename Enum<T>::Iterator(static_cast<std::underlying_type_t<T>>(T::Begin));
}

template <typename T>
constexpr typename Enum<T>::Iterator end(Enum<T>)
{
  return typename Enum<T>::Iterator(static_cast<std::underlying_type_t<T>>(T::End));
}

And we can easily iterate over all elements of any enum containing Begin and End using our wrapper:

for (auto item : Enum<MyType>)
{
  // используем item:
  // код здесь внутри будет вызван три раза: для One, Two и Three
}

And as a bonus, you can add the size() method for our Enum class, suddenly you need:

static constexpr std::size_t size()
{
  return static_cast<std::underlying_type_t<T>>(T::End) - static_cast<std::underlying_type_t<T>>(T::Begin);
}

Related posts