Iterate, dispatch, and store enum values
magic_enum provides a suite of utilities for compile-time and runtime interaction with enumerations. These tools allow you to iterate over all enumerators, dispatch logic based on runtime enum values, and store data in specialized containers that use enums as keys.
Compile-time Iteration
When you need to perform an action for every value in an enum—such as generating a report or initializing a lookup table—magic_enum::enum_for_each provides a compile-time iteration mechanism.
The function accepts a callable (usually a lambda) that receives a magic_enum::enum_constant<V>. To access the actual enum value, you must invoke this constant using the () operator. This is required because the parameter is a wrapper object, not the enum value itself.
#include <iostream>
#include <string>
#include <vector>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_utility.hpp>
enum class Color { Red = 1, Green = 2, Blue = 4 };
int main() {
std::vector<std::string> names;
// Iterate over all Color values and collect their names.
// The lambda parameter 'c' is a magic_enum::enum_constant.
magic_enum::enum_for_each<Color>([&names](auto c) {
// c() returns the actual Color value.
auto name = magic_enum::enum_name(c());
names.emplace_back(name);
});
for (const auto& name : names) {
std::cout << name << " "; // Prints: Red Green Blue
}
return 0;
}
Internally, magic_enum::enum_for_each uses std::index_sequence to expand the enum values at compile time. If your lambda returns a value, enum_for_each will return a std::array (if all return types are identical) or a std::tuple containing the results.
Runtime Dispatching
Standard C++ switch statements require case labels to be constant expressions. If you have a runtime enum value and want to execute code that depends on compile-time information (like the enum's name or a template specialization), magic_enum::enum_switch acts as a bridge.
To ensure safety, you must specify an explicit result type (e.g., std::string) as the first template argument. This prevents issues like returning a std::string_view that might point to a null pointer if the enum value is invalid. The lambda passed to enum_switch must also declare a trailing return type.
#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 based on the runtime value of 'c'.
// We specify <std::string> to handle invalid enums safely.
auto result = magic_enum::enum_switch<std::string>(
[](auto val) -> std::string {
// val is a magic_enum::enum_constant.
// We can use it in compile-time contexts.
constexpr Color color = val();
if constexpr (color == Color::Red) {
return "Stop";
} else {
return std::string(magic_enum::enum_name(val()));
}
},
c
);
std::cout << "Action: " << result << std::endl; // Prints: Action: Green
return 0;
}
If enum_switch is called with a value that does not exist in the enum, it returns a default-constructed instance of the specified result type.
Enum-Keyed Arrays
The magic_enum::containers::array class is a wrapper around std::array that allows you to use enum values as indices. This eliminates the need for manual casting to underlying integer types and prevents off-by-one errors when the enum values are not contiguous.
The recommended usage pattern is to default-construct the array and then assign values using the enum members as keys.
#include <iostream>
#include <cassert>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>
enum class Direction { North, South, East, West };
int main() {
// Create an array mapping Direction to string descriptions.
magic_enum::containers::array<Direction, const char*> descriptions;
// Assign values using enum keys.
descriptions[Direction::North] = "Heading Up";
descriptions[Direction::South] = "Heading Down";
descriptions[Direction::East] = "Heading Right";
descriptions[Direction::West] = "Heading Left";
// Access values safely.
std::cout << "North is: " << descriptions.at(Direction::North) << std::endl;
// The size is automatically determined by the number of enum values.
assert(descriptions.size() == 4);
return 0;
}
The containers::array uses magic_enum::enum_index internally to map the enum value to the correct position in the underlying std::array. If you use at(), the container performs a bounds check and throws std::out_of_range if the enum value is unrecognized.
Optimized Enum Sets
For storing a collection of unique enum values, magic_enum::containers::set provides a high-performance alternative to std::set<E>. It is implemented using a bitset, making operations like insert, erase, and contains extremely efficient.
#include <iostream>
#include <cassert>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>
enum class Permission { Read, Write, Execute };
int main() {
magic_enum::containers::set<Permission> my_perms;
my_perms.insert(Permission::Read);
my_perms.insert(Permission::Write);
if (my_perms.contains(Permission::Read)) {
std::cout << "Read access granted." << std::endl;
}
// Iteration yields the enum values in the order they appear in the enum definition.
for (Permission p : my_perms) {
std::cout << "Has: " << magic_enum::enum_name(p) << std::endl;
}
assert(my_perms.size() == 2);
return 0;
}
Because it uses a bitset, the memory footprint of containers::set is minimal, typically requiring only a few bytes depending on the number of enumerators. It supports standard container operations and provides a type-safe way to manage flags or groups of enum values.