2

I am finding that all of my standard techniques for iterating over regular enums unfortunately do NOT work on enum classes since enum classes do not implicitly convert to integers.

NOT a duplicate of How can I iterate over an enum?, since I'm asking about an enum class (ie: a strongly-typed enum) and they are asking about a regular enum (ie: a weakly-typed enum).

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • A pretty common method is to use a static table containing all the enum values, and iterate over that. It's less grimy than using explicit conversion between integer type and the enum class, and iterating over the integer range. Of course, it comes at the expense of storage and takes extra effort to maintain the table if you add/remove/rename values in your enum. – paddy Oct 29 '21 at 01:32
  • @paddy, can you provide an example? – Gabriel Staples Oct 29 '21 at 01:49

2 Answers2

7

Another alternative is to use C++20 ranges to compose an enum range:

constexpr inline auto enum_range = [](auto front, auto back) {
  return std::views::iota(std::to_underlying(front), std::to_underlying(back) + 1) 
       | std::views::transform([](auto e) { return decltype(front)(e); }); 
};

Then you can iterate the enum like this:

enum class color { red, yellow, green, blue };
for (const auto e : enum_range(color::red, color::blue))
  // ...

demo.

康桓瑋
  • 33,481
  • 5
  • 40
  • 90
1

This is the most-readable and simplest approach I could come up with, but I am open to other peoples' example solutions.

I find this approach to be easy-to-use, similar to my C approach (making it more-portable and more-recognizable), and suitable to C++. It compiles with the -Wall -Wextra -Werror compiler build options.

enum class MyErrorType 
{
    SOMETHING_1 = 0,
    SOMETHING_2,
    SOMETHING_3,
    SOMETHING_4,
    SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    _COUNT,
    // helpers for iterating over the enum
    begin = 0,
    end = _COUNT,
};

for (MyErrorType myErrorType = (MyErrorType)0; 
        myErrorType < MyErrorType::_COUNT;
        myErrorType = static_cast<MyErrorType>((size_t)myErrorType + 1)) 
{
    switch (myErrorType) 
    {
        case MyErrorType::SOMETHING_1:
            break;
        case MyErrorType::SOMETHING_2:
            break;
        case MyErrorType::SOMETHING_3:
            break;
        case MyErrorType::SOMETHING_4:
            break;
        case MyErrorType::SOMETHING_5:
            break;
        case MyErrorType::_COUNT:
            // This case will never be reached. It is included only so that when
            // compiling with `-Wall -Wextra -Werror` build flags you get the
            // added bonus of covering all switch cases (withOUT unnecessarily
            // relying on a `default` case which would break this feature!), so
            // if you ever add a new element to the enum class but forget to
            // add it here to the switch case the compiler will THROW AN ERROR.
            // This is an added safety benefit to force you to keep your enum
            // and the switch statement in-sync! It's a technique commonly used
            // in C as well.
            break;
    }
}

Read my comments for the MyErrorType::_COUNT case above! If you are using the compiler's -Wall -Wextra -Werror compiler options but do NOT include this case in the switch statement (since those build options require you to cover ALL enum cases in ALL switch statements!), the compiler will throw the following error and stop! This is a great safety feature to ensure you keep the enum definition and all switch cases in-sync, handling all possible enums in all of your switch statements. Here is the compiler error thrown by LLVM's clang compiler (an alternative to gcc):

../my_file.cpp:11:16: error: enumeration value ‘_COUNT’ not handled in switch [-Werror=switch]
   11 |         switch (myErrorType) {
      |                ^

One more tiny improvement over the code above, for clarity, would be to add begin and end elements to your enum like this:

enum class MyErrorType 
{
    SOMETHING_1 = 0,
    SOMETHING_2,
    SOMETHING_3,
    SOMETHING_4,
    SOMETHING_5,
    /// Not a valid value; this is the number of members in this enum
    _COUNT,
    // helpers for iterating over the enum
    begin = 0,
    end = _COUNT,
};

...so that you can iterate over the enum as follows. The incrementing part of the for loop still is a bit cumbersome with all of the required casts, but the initial state and end condition check are at least much clearer now, since they use MyErrorType::begin and MyErrorType::end:

for (MyErrorType myErrorType = MyErrorType::begin; 
        myErrorType < MyErrorType::end;
        myErrorType = static_cast<MyErrorType>((size_t)myErrorType + 1)) 
{
    switch (myErrorType) 
    {
        case MyErrorType::SOMETHING_1:
            break;
        case MyErrorType::SOMETHING_2:
            break;
        case MyErrorType::SOMETHING_3:
            break;
        case MyErrorType::SOMETHING_4:
            break;
        case MyErrorType::SOMETHING_5:
            break;
        case MyErrorType::_COUNT:
            // This case will never be reached.
            break;
    }
}

Related:

  1. Common techniques for iterating over enums (as opposed to enum classes): How can I iterate over an enum?
    1. [my answer] How can I iterate over an enum?
  2. My answer on some of the differences between enum classes (strongly-typed enums) and regular enums (weakly-typed enums) in C++: How to automatically convert strongly typed enum into int?
  3. Some of my personal notes on the -Wall -Wextra -Werror and other build options, from my eRCaGuy_hello_world repo.
  4. Incrementation and decrementation of “enum class”

Other keywords: common way to iterate over enum or enum class in C or C++; best way to iterate over enum class in C++; enum class C++ iterate; c++ iterate over enum class

Gabriel Staples
  • 36,492
  • 15
  • 194
  • 265
  • 1
    If this is your preferred style, you may want to consider some small tweaks by defining "begin" and "end" values for your enum class (similar to C++ iterators) and also overload `operator++`. It keeps the loop a bit cleaner. Example [here](https://godbolt.org/z/xecv94nGM). – paddy Oct 29 '21 at 02:12
  • @paddy, good approach [in your example](https://godbolt.org/z/xecv94nGM). However, if I have 10 different `enum class`es, I'd find that approach very difficult to maintain all of the different increment operator overload functions. Is there any way to create a **single macro or template** which covers *all* `enum class` types to increment an enum class variable of any of those types? – Gabriel Staples Oct 29 '21 at 20:23
  • A simple extension of the idea would be something like [this](https://godbolt.org/z/vxoT7Eeoe) by just creating a template class to wrap the enum type for iteration. I don't personally like the macros, but you could play around and make them better. I prefer to see actual enum class definitions instead of having that syntax hidden in a macro. – paddy Oct 31 '21 at 23:00