Iterate, dispatch, and store enum values
When you need to perform operations across all members of an enumeration or store data associated with specific enum keys, standard C++ requires manual maintenance of loops and arrays. magic_enum provides specialized utilities and containers that automate these tasks by leveraging compile-time reflection.
Iterating Over Enum Values
If you need to execute logic for every value in an enum—such as registering handlers or generating UI elements—magic_enum::enum_for_each provides a type-safe way to iterate. Unlike a standard loop, it passes an enum_constant wrapper to your lambda, which allows you to access the enum value at compile-time.
To use the value inside the lambda, you must invoke the parameter (e.g., val()). This is required because the parameter is an object, not the enum value itself.
#include <iostream>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_utility.hpp>
enum class Color { RED, GREEN, BLUE };
int main() {
// Iterates over all values defined in Color.
magic_enum::enum_for_each<Color>([](auto val) {
// val is an enum_constant; invoke it to get the value for enum_name.
std::cout << magic_enum::enum_name(val()) << std::endl;
});
return 0;
}
Internally, enum_for_each uses the reflected enum_values<E>() sequence to apply your function to each constant.
Dispatching with Compile-Time Switch
When you have a runtime enum value and need to execute code that requires the value as a compile-time constant (for example, to use it as a template argument), magic_enum::enum_switch acts as a bridge. It generates a switch-case structure that maps the runtime value to a compile-time enum_constant.
You must specify an explicit return type for the switch (e.g., std::string) and ensure the lambda uses a trailing return type to match.
#include <iostream>
#include <string>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_switch.hpp>
enum class Color { RED, GREEN, BLUE };
int main() {
Color c = Color::GREEN;
// Dispatch runtime value 'c' to a compile-time context.
auto name = magic_enum::enum_switch<std::string>([](auto val) -> std::string {
// val() is a compile-time constant here.
if constexpr (val() == Color::RED) {
return "Crimson";
} else {
return std::string(magic_enum::enum_name(val()));
}
}, c);
std::cout << "Result: " << name << std::endl;
return 0;
}
This mechanism ensures that even if the runtime value is invalid, the enum_switch returns a default-constructed instance of your result type instead of causing undefined behavior.
Storing Data in Enum-Indexed Arrays
Mapping data to enum values often involves std::array, but this requires manual casting of enums to integers and risks out-of-bounds errors if the enum range changes. magic_enum::containers::array wraps std::array and allows you to use enum values directly as keys.
The container is typically default-constructed, and values are assigned using the enum members.
#include <iostream>
#include <string>
#include <cassert>
#include <magic_enum/magic_enum_containers.hpp>
enum class Color { RED, GREEN, BLUE };
int main() {
// Create an array where keys are Color enums and values are strings.
magic_enum::containers::array<Color, std::string> color_hints;
// Assign values using enum keys directly.
color_hints[Color::RED] = "Stop";
color_hints[Color::GREEN] = "Go";
color_hints[Color::BLUE] = "Caution";
// Access is type-safe and does not require static_cast.
assert(color_hints[Color::RED] == "Stop");
std::cout << "Green means: " << color_hints.at(Color::GREEN) << std::endl;
return 0;
}
The magic_enum::containers::array uses an internal index_type (by default magic_enum::containers::default_indexing) to map the enum's position in enum_values<E>() to the underlying std::array index.
Managing Collections of Enum Values
When you need to track a subset of available enum values—such as active flags or selected options—magic_enum::containers::set provides a memory-efficient collection. It behaves like std::set but is optimized for the fixed range of an enumeration.
#include <iostream>
#include <cassert>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>
enum class Color { RED, GREEN, BLUE };
int main() {
magic_enum::containers::set<Color> palette;
// Add values to the set.
palette.insert(Color::RED);
palette.insert(Color::BLUE);
// Check for existence.
if (palette.contains(Color::RED)) {
std::cout << "Palette contains RED" << std::endl;
}
assert(!palette.contains(Color::GREEN));
// Iterate over the selected values.
for (auto c : palette) {
std::cout << "Active color: " << magic_enum::enum_name(c) << std::endl;
}
return 0;
}
The set implementation is backed by a bitset, making it extremely compact while maintaining the standard container interface for insertion and iteration.