Skip to main content

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.