Iterate, dispatch, and store enum values
Iterating over enumerations or dispatching logic based on enum values typically requires manual maintenance of switch statements or arrays. magic_enum provides utilities to automate these patterns using compile-time reflection, ensuring that your logic stays in sync with your enum definitions.
Iterating over Enum Values
When you need to perform an action for every member of an enumeration—such as generating a list of names for a UI or initializing a registry—enum_for_each provides a type-safe way to iterate through all reflected values.
The function takes a callable (usually a lambda) that accepts a magic_enum::enum_constant. Because this parameter is a compile-time constant, you must invoke it (e.g., val()) to retrieve the actual enum value before passing it to other functions like enum_name.
#include <iostream>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_utility.hpp>
enum class Color { RED, GREEN, BLUE };
int main() {
// Iterate over all values of the Color enum.
// The lambda parameter 'val' is a magic_enum::enum_constant.
magic_enum::enum_for_each<Color>([](auto val) {
constexpr Color c = val();
std::cout << magic_enum::enum_name(c) << " = " << static_cast<int>(c) << std::endl;
});
return 0;
}
Internally, enum_for_each uses detail::for_each (found in magic_enum/magic_enum_utility.hpp) to expand the enum values into a sequence of calls. If your lambda returns a value, enum_for_each will collect those results into a std::array (if all return types are the same) or a std::tuple.
Dispatching with Enum Switch
Standard C++ switch statements cannot return values directly and often lead to "missing case" warnings or runtime errors if an invalid enum value is encountered. enum_switch solves this by providing a functional dispatch mechanism that can return a value and handle invalid inputs safely.
To ensure safety, you should specify an explicit result type (like std::string). This prevents magic_enum from returning a default-constructed value that might be unsafe, such as a std::string_view pointing to a null pointer.
#include <iostream>
#include <string>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_switch.hpp>
enum class Status { OK, ERROR, PENDING };
std::string get_status_message(Status s) {
// Use enum_switch to dispatch logic based on the runtime value of 's'.
// We specify std::string as the explicit result type for safety.
return magic_enum::enum_switch<std::string>([](auto val) -> std::string {
constexpr Status status = val();
switch (status) {
case Status::OK: return "Operation successful";
case Status::ERROR: return "An error occurred";
case Status::PENDING: return "Waiting for result";
}
return "Unknown status";
}, s);
}
int main() {
std::cout << get_status_message(Status::OK) << std::endl;
return 0;
}
The enum_switch implementation in magic_enum/magic_enum_switch.hpp uses detail::constexpr_switch to perform a linear search or a hash-based lookup (if enabled) to find the matching case at runtime, then invokes the provided lambda with the corresponding compile-time constant.
Storing Values in Enum-Aware Arrays
Mapping enum values to data often involves std::array, but this requires manual indexing and is prone to off-by-one errors if the enum values are not contiguous or don't start at zero. magic_enum::containers::array acts as a drop-in replacement for std::array that is indexed directly by enum values.
You can default-construct the array and then assign values using the enum members as keys.
#include <iostream>
#include <string>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>
enum class Fruit { APPLE, BANANA, ORANGE };
int main() {
// Create an array mapping Fruit enums to their string names.
magic_enum::containers::array<Fruit, std::string> fruit_names;
// Assign values using enum keys.
fruit_names[Fruit::APPLE] = "Red Apple";
fruit_names[Fruit::BANANA] = "Yellow Banana";
fruit_names[Fruit::ORANGE] = "Juicy Orange";
// Access values directly with the enum.
std::cout << "Fruit: " << fruit_names[Fruit::BANANA] << std::endl;
return 0;
}
The containers::array class (defined in magic_enum/magic_enum_containers.hpp) uses a detail::indexing strategy to map enum values to the underlying std::array indices. By default, it uses enum_index to perform this mapping, ensuring that the array size matches the number of reflected enum members exactly.
Managing Unique Enum Sets
If you need to store a collection of unique enum values, magic_enum::containers::set provides a container similar to std::set but optimized for enums. It uses a bitset internally for high performance and low memory overhead.
#include <iostream>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>
enum class Permission { READ, WRITE, EXECUTE };
int main() {
// Initialize a set with specific permissions.
magic_enum::containers::set<Permission> my_permissions{Permission::READ, Permission::WRITE};
// Check for existence using contains().
if (my_permissions.contains(Permission::WRITE)) {
std::cout << "Write access granted." << std::endl;
}
// Add new values.
my_permissions.insert(Permission::EXECUTE);
std::cout << "Total permissions: " << my_permissions.size() << std::endl;
return 0;
}
The containers::set class in magic_enum/magic_enum_containers.hpp is built on top of magic_enum::containers::bitset. It provides a standard-compliant interface including insert, erase, and find, while maintaining the efficiency of bitwise operations. Unlike a raw bitmask, it allows you to iterate over the set and retrieve the actual enum values.