Iterate, dispatch, and store enum values
Iterating over enumerators, dispatching logic based on runtime enum values, and storing data in enum-indexed containers are core tasks when working with enums in C++. magic_enum provides a set of utilities and containers designed to handle these operations safely and efficiently.
Iterating over Enum Values
When you need to perform an action for every enumerator in an enum, magic_enum::enum_for_each provides a compile-time iteration mechanism. It accepts a callable (such as a lambda) and invokes it for each enum value.
The callable must accept a magic_enum::enum_constant<V> as its argument. This allows the compiler to know the specific enum value at compile-time within the lambda body.
#include <magic_enum/magic_enum_utility.hpp>
#include <iostream>
#include <string_view>
enum class Color { RED, GREEN, BLUE };
void print_colors() {
// Iterates over RED, GREEN, BLUE
magic_enum::enum_for_each<Color>([](auto val) {
// val is a magic_enum::enum_constant<Color::VALUE>
constexpr Color c = val;
std::cout << magic_enum::enum_name(c) << " ";
});
}
If the lambda returns a value, magic_enum::enum_for_each can collect these results into a std::array (if all return types are the same) or a std::tuple (if they differ).
// Collects names of all colors into a std::array<std::string_view, 3>
constexpr auto color_names = magic_enum::enum_for_each<Color>([](auto val) {
return magic_enum::enum_name<decltype(val)::value>();
});
Dispatching with Enum Switch
The magic_enum::enum_switch function allows you to dispatch logic based on a runtime enum value, similar to a switch statement but with the ability to return values from lambdas.
To ensure safety, you should always:
- Specify an explicit
Resulttype. - Provide a default value to handle cases where the enum value might be invalid or not handled by your logic.
Specifying std::string as the result type is safer than std::string_view when dealing with potentially invalid enums, as it prevents the creation of a std::string_view from a null pointer.
#include <magic_enum/magic_enum_switch.hpp>
#include <string>
std::string get_color_description(Color c) {
return magic_enum::enum_switch<std::string>(
[](auto val) {
constexpr Color color = val;
if constexpr (color == Color::RED) return "The color of fire";
if constexpr (color == Color::GREEN) return "The color of grass";
if constexpr (color == Color::BLUE) return "The color of the sky";
return "Unknown color";
},
c,
std::string{"Invalid color"} // Default value
);
}
Internally, magic_enum::enum_switch uses magic_enum::detail::constexpr_switch to map the runtime value to the appropriate compile-time constant and invoke the provided callable.
Storing Data in Enum-Indexed Arrays
The magic_enum::containers::array class is a wrapper around std::array that uses enum values as indices. It ensures that the array size matches the number of enumerators and provides type-safe access.
You can access elements using the at() method (which throws std::out_of_range for invalid enums) or the [] operator (which uses MAGIC_ENUM_ASSERT for bounds checking).
#include <magic_enum/magic_enum_containers.hpp>
struct RGB { int r, g, b; };
void use_array() {
magic_enum::containers::array<Color, RGB> color_data;
color_data[Color::RED] = {255, 0, 0};
color_data.at(Color::GREEN) = {0, 255, 0};
// Initialization using to_array
auto initialized_data = magic_enum::containers::to_array<Color>({
RGB{255, 0, 0}, // RED
RGB{0, 255, 0}, // GREEN
RGB{0, 0, 255} // BLUE
});
}
The magic_enum::containers::array requires the enum to be reflected and non-empty. It uses an Index strategy (defaulting to magic_enum::containers::default_indexing) to map enum values to array offsets.
Managing Collections with Enum Sets
For storing a unique collection of enum values, magic_enum::containers::set provides a bitset-backed container that mimics the std::set interface but is optimized for enums.
#include <magic_enum/magic_enum_containers.hpp>
void use_set() {
magic_enum::containers::set<Color> active_colors;
active_colors.insert(Color::RED);
active_colors.insert(Color::BLUE);
if (active_colors.contains(Color::RED)) {
// ...
}
// Iteration only visits inserted elements
for (Color c : active_colors) {
std::cout << magic_enum::enum_name(c) << "\n";
}
}
The magic_enum::containers::set uses a magic_enum::containers::bitset internally to track which enumerators are present. This makes operations like insert, erase, and contains extremely efficient (O(1)). Iteration is performed using a magic_enum::containers::detail::FilteredIterator, which skips over bits that are not set.