I am finding that all of my standard techniques for iterating over regular enum
s unfortunately do NOT work on enum class
es 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).
Limitation for this entire answer: the enum has to be continuous, with no gaps (skipped integers not assigned to an enum member) between the two enum values you want to iterate between.
In C++20 or later you can write an enum_range()
range generator function to allow you to iterate over an enum
or enum class
(scoped enum) using modern range-based for loops like this:
for (const MyErrorType enum_element :
enum_range(MyErrorType::begin, MyErrorType::end))
{
// switch statement here, operating on `enum_element`
}
Here is the custom enum_range()
range generator function definition that works in C++20 or later. Comments in the function body have been removed for brevity:
// Generate a C++20 "range" iterating from the enum value `first` to `last`,
// inclusive, to be used in modern C++ range-based for loops.
// - This is also using the C++20 feature of "abbreviated function templates",
// or "template argument deduction for functions", where `auto` can be used
// for all input parameter types and for the return type in place of making
// this a function template.
constexpr inline auto enum_range(auto first, auto last)
{
auto enum_range =
std::views::iota(
static_cast<std::underlying_type_t<decltype(first)>>(first),
static_cast<std::underlying_type_t<decltype(last)>>(last) + 1
)
| std::views::transform([](auto enum_val)
{
return (decltype(first))enum_val;
}
);
return enum_range;
};
Explanation:
enum_range()
is a custom "abbreviated function template" function that generates a C++20 "range" object for iterating over enum values. It uses std::views::iota()
(AKA: std::ranges::views::iota()
) to create a sequence of underlying integer values from enum value MyErrorType::begin
to MyErrorType::end
, and then pipes that range to std::views::transform()
with a lambda function as the operator argument to transform those integers back into the enum type. Piping the sequence to the std::views::transform()
function is what generates the range object, which is then returned from the enum_range()
function and used as the range container object in the range-based for loop for iterating through each enum element.
Note: for those coming from C, don't confuse std::ranges::views::iota()
with <cstdlib>
's itoa()
function. In the former, iota
refers to the Greek letter "iota", meaning: "a small increment or a sequence of consecutive values", and in the latter C function it stands for "integer to ASCII (C-string)".
(End of the quick summary)
See below for details and alternatives for earlier versions of C++.
In C++20 or later, you can iterate over an enum
or enum class
(scoped enum) using range-based for loops by writing a custom range-generator function. While this range generator function is complicated to write, it is simple to use. And, 康桓瑋 (Kang Huanwei) paved the way in his answer here. That answer is where I first learned about ranges in C++.
For C++11 or later, you can use a standard for loop with some casts. While this is less "modern", its advantages are that it is widely available in C++11 or later and is far more accessible and understandable to the average C++ programmer.
Since enum class
es (scoped enums) were not introduced until C++11, this answer does not work for C++03 or earlier at all.
For all of my code below, see these files in my eRCaGuy_hello_world repo:
Note that:
The namespace alias
std::views
is provided as a shorthand forstd::ranges::views
.Defined in header
<ranges>
namespace std { namespace views = ranges::views; }
Source: https://en.cppreference.com/w/cpp/ranges
So, for this enum:
enum class MyErrorType
{
SOMETHING_1 = 0,
SOMETHING_2,
SOMETHING_3,
SOMETHING_4,
SOMETHING_5,
// Helpers for iterating over the enum:
// - Note: adding these helpers adds no new enum values, since `begin`
// already has the same value as `SOMETHING_1`, and `end` already has the
// same value as `SOMETHING_5`. These are just aliased names is all.
begin = 0,
end = SOMETHING_5,
};
*Use this range generator function:
// C++23 or later: using `std::to_underlying()`, a C++23 feature:
#include <ranges>
// Generate a C++20 "range" iterating from the enum value `first` to `last`,
// inclusive, to be used in modern C++ range-based for loops.
// - This is also using the C++20 feature of "abbreviated function templates",
// or "template argument deduction for functions", where `auto` can be used
// for all input parameter types and for the return type in place of making
// this a function template.
constexpr inline auto enum_range(auto first, auto last)
{
// Note that "ranges" exist only in C++20 or later
auto enum_range =
// `std::views::iota` is a C++20 range generator that auto-generates a
// sequence of values from (param1) to (param2 - 1).
// - See: https://en.cppreference.com/w/cpp/ranges/iota_view
// - The word "iota" refers to the Greek letter "iota" which apparently
// is often used in math and computer science to represent a small
// increment or a sequence of consecutive values.
// - In range factories, `|` is apparently the "pipe" operator, which is
// used to chain together range operations.
std::views::iota(
std::to_underlying(first),
std::to_underlying(last) + 1
)
| std::views::transform([](auto enum_val)
{
return (decltype(first))enum_val;
}
);
return enum_range;
};
...to iterate over the enum class using a modern range-based for loop like this:
for (const MyErrorType e : enum_range(MyErrorType::begin, MyErrorType::end))
{
switch (e)
{
case MyErrorType::SOMETHING_1:
printf("MyErrorType::SOMETHING_1\n");
break;
case MyErrorType::SOMETHING_2:
printf("MyErrorType::SOMETHING_2\n");
break;
case MyErrorType::SOMETHING_3:
printf("MyErrorType::SOMETHING_3\n");
break;
case MyErrorType::SOMETHING_4:
printf("MyErrorType::SOMETHING_4\n");
break;
case MyErrorType::SOMETHING_5:
printf("MyErrorType::SOMETHING_5\n");
break;
}
}
In C++20 and earlier you don't have access to std::to_underlying(my_variable)
, so you must use static_cast<std::underlying_type_t<decltype(my_variable)>>(my_variable)
instead. So, here is the range generator function that works in C++20 or later:
// For C++20 range-based views:
// Generate a C++20 "range" iterating from the enum value `first` to `last`,
// inclusive, to be used in modern C++ range-based for loops.
// - This is also using the C++20 feature of "abbreviated function templates",
// or "template argument deduction for functions", where `auto` can be used
// for all input parameter types and for the return type in place of making
// this a function template.
constexpr inline auto enum_range(auto first, auto last)
{
// Note that "ranges" exist only in C++20 or later
auto enum_range =
// `std::views::iota` is a C++20 range generator that auto-generates a
// sequence of values from (param1) to (param2 - 1).
// - See: https://en.cppreference.com/w/cpp/ranges/iota_view
// - The word "iota" refers to the Greek letter "iota" which apparently
// is often used in math and computer science to represent a small
// increment or a sequence of consecutive values.
// - In range factories, `|` is apparently the "pipe" operator, which is
// used to chain together range operations.
std::views::iota(
static_cast<std::underlying_type_t<decltype(first)>>(first),
static_cast<std::underlying_type_t<decltype(last)>>(last) + 1
)
| std::views::transform([](auto enum_val)
{
return (decltype(first))enum_val;
}
);
return enum_range;
};
Everything else is the same as above.
The "ranges" library (see: https://en.cppreference.com/w/cpp/ranges) was introduced in C++20, so you can't use it in C++11, C++14, or C++17. The following therefore works in C++11 or later, including in C++20 and C++23.
While the above technique is very easy to use, the range generator is very hard to initially write, and to understand. Therefore, the following approach has the following benefits:
Additional info (also applies to the above examples): it compiles with the -Wall -Wextra -Werror
compiler build options, which ensure that a compile-time error is thrown if you forget to handle any of the enum values in your switch statements. 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.
From enum_class_iterate.cpp in my eRCaGuy_hello_world repo:
///usr/bin/env ccache g++ -Wall -Wextra -Werror -O3 -std=gnu++17 "$0" -o /tmp/a && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887
#include <cstdio> // For `printf()`
enum class MyErrorType
{
SOMETHING_1 = 0,
SOMETHING_2,
SOMETHING_3,
SOMETHING_4,
SOMETHING_5,
// Helpers for iterating over the enum:
// - Note: adding these helpers adds no new enum values, since `begin`
// already has the same value as `SOMETHING_1`, and `end` already has the
// same value as `SOMETHING_5`. These are just aliased names is all.
begin = 0,
end = SOMETHING_5,
};
int main()
{
printf("C++ enum class iteration demo.\n");
// Iterate over the enum class
// Option 1
for (MyErrorType myErrorType = MyErrorType::begin;
myErrorType <= MyErrorType::end;
myErrorType = static_cast<MyErrorType>(
static_cast<size_t>(myErrorType) + 1))
{
switch (myErrorType)
{
case MyErrorType::SOMETHING_1:
printf("MyErrorType::SOMETHING_1\n");
break;
case MyErrorType::SOMETHING_2:
printf("MyErrorType::SOMETHING_2\n");
break;
case MyErrorType::SOMETHING_3:
printf("MyErrorType::SOMETHING_3\n");
break;
case MyErrorType::SOMETHING_4:
printf("MyErrorType::SOMETHING_4\n");
break;
case MyErrorType::SOMETHING_5:
printf("MyErrorType::SOMETHING_5\n");
break;
}
}
}
Make the file above executable and run it as follows. Tested in Linux Ubuntu 22.04:
# Ensure you have `ccache`
sudo apt update
sudo apt install ccache
# make the file executable
chmod +x enum_class_iterate.cpp
# compile and run it
./enum_class_iterate.cpp
Sample run and output:
eRCaGuy_hello_world/cpp$ ./enum_class_iterate.cpp
C++ enum class iteration demo.
MyErrorType::SOMETHING_1
MyErrorType::SOMETHING_2
MyErrorType::SOMETHING_3
MyErrorType::SOMETHING_4
MyErrorType::SOMETHING_5
count
enum class value, but makes the last portion of the enum class definition always the same for all enum classes, which is niceThis is my preferred way to define an enum class in C++, for all versions of C++, including C++11 through C++23, inclusive. For C++20, rather than the manual iteration approach below, however, I do like to use the enum_range()
range generator function I showed above to iterate using modern range-based for loops.
Note that you can also optionally add a count value if you need to use it anywhere to get the number of valid enum values in your enum class. This also has the really nice added benefit of making the last portion of the enum class definition, starting with count,
always the exact same for all enum classes, so you can easily copy-paste this code and recognize it throughout your code base. In the first example above, in end = SOMETHING_5,
, the SOMETHING_5
enum value must be manually updated for all of your enums, which is error-prone. So, this is my preferred way to define an enum class in C++:
enum class MyErrorType2
{
SOMETHING_1 = 0,
SOMETHING_2,
SOMETHING_3,
SOMETHING_4,
SOMETHING_5,
// Helpers
count,
begin = 0,
end = count - 1,
};
Iteration over the enum is still exactly the same as above, except you must add a dummy "nothing to do" case for the count
value in your switch statement, in order to cover all possible values in the enum class:
// Option 2: same as above, except we must also include the `count` value
// as a switch case.
for (MyErrorType2 myErrorType2 = MyErrorType2::begin;
myErrorType2 <= MyErrorType2::end;
myErrorType2 = static_cast<MyErrorType2>(
static_cast<size_t>(myErrorType2) + 1))
{
switch (myErrorType2)
{
case MyErrorType2::SOMETHING_1:
printf("MyErrorType2::SOMETHING_1\n");
break;
case MyErrorType2::SOMETHING_2:
printf("MyErrorType2::SOMETHING_2\n");
break;
case MyErrorType2::SOMETHING_3:
printf("MyErrorType2::SOMETHING_3\n");
break;
case MyErrorType2::SOMETHING_4:
printf("MyErrorType2::SOMETHING_4\n");
break;
case MyErrorType2::SOMETHING_5:
printf("MyErrorType2::SOMETHING_5\n");
break;
case MyErrorType2::count:
// Nothing to do
break;
}
}
If you forget the case MyErrorType2::count:
case in the switch statement but are compiling using the compiler's -Wall -Wextra -Werror
options, you will get this error: error: enumeration value ‘count’ not handled in switch [-Werror=switch]
, as shown here:
eRCaGuy_hello_world/cpp$ ./enum_class_iterate.cpp
./enum_class_iterate.cpp: In function ‘int main()’:
./enum_class_iterate.cpp:127:16: error: enumeration value ‘count’ not handled in switch [-Werror=switch]
127 | switch (myErrorType2)
| ^
cc1plus: all warnings being treated as errors
Compiling with -Wall -Wextra -Werror
and not using a default
switch case in any switch statements is therefore the recommended approach to help you always keep your enum definitions and switch statements in-sync, covering all possible enum values.
*****+ This really helpful answer by 康桓瑋 (Kang Huanwei) here
C++20 views and ranges: https://en.cppreference.com/w/cpp/ranges
Algorithm: https://en.cppreference.com/w/cpp/algorithm/transform
Utility: https://en.cppreference.com/w/cpp/utility/to_underlying
GitHub Copilot:
Prompt:
In
std::views::iota
, what does iota stand for?
Answer (emphasis added):
In
std::views::iota
, the term "iota" is derived from the Greek letter "ι" (iota), which is often used in mathematics and computer science to represent a small increment or a sequence of consecutive values. In the context of C++20 ranges,std::views::iota
generates an infinite sequence of values starting from a given initial value.
Abbreviated Function Templates and Constrained Auto - https://devblogs.microsoft.com/cppblog/abbreviated-function-templates-and-constrained-auto/
enum
s (as opposed to enum class
es): How can I iterate over an enum?
enum class
es (strongly-typed enums) and regular enum
s (weakly-typed enums) in C++: How to automatically convert strongly typed enum into int?-Wall -Wextra -Werror
and other build options, from my eRCaGuy_hello_world repo.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