Skip to main content

Iterate, dispatch, and store enum values

When you need to process every member of an enumeration or store data associated with specific enum keys, standard C++ often requires manual maintenance of arrays or switch statements. The magic_enum library provides utilities to automate these patterns, ensuring that your iteration, dispatching, and storage logic remains synchronized with your enum definitions.

Iterating Over Enum Values

To execute logic for every value in an enum, use magic_enum::enum_for_each. This function iterates through all values defined in the enum and applies a provided callable.

Internally, enum_for_each passes a magic_enum::enum_constant<V> wrapper to your lambda. This wrapper is a type-level representation of the enum value. To access the actual enum value for use in functions like magic_enum::enum_name, you must invoke the wrapper as a function (e.g., val()).

#include <iostream>
#include <string>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_utility.hpp>

enum class Color { Red, Green, Blue };

int main() {
// Iterate over all Color values and print their names
magic_enum::enum_for_each<Color>([](auto val) {
// val is an enum_constant; invoke it to get the enum value
auto name = magic_enum::enum_name(val());
std::cout << name << " ";
});
// Output: Red Green Blue
return 0;
}

Dispatching with Compile-Time Safety

The magic_enum::enum_switch function provides a way to dispatch logic based on an enum value at runtime while maintaining compile-time awareness of the cases. This is particularly useful when you want to map enum values to other types or behaviors without writing a manual switch block.

When using enum_switch, you should specify an explicit result type (e.g., magic_enum::enum_switch<std::string>). This ensures that if an invalid or unrecognized enum value is passed, the function returns a default-constructed instance of that type (like an empty string) instead of triggering undefined behavior. Your lambda must also include a trailing return type that matches this result type.

#include <iostream>
#include <string>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_switch.hpp>

enum class Status { Active, Suspended, Terminated };

int main() {
Status s = Status::Suspended;

// Dispatch based on runtime value with a safe fallback
std::string description = magic_enum::enum_switch<std::string>(
[](auto val) -> std::string {
switch (val()) {
case Status::Active: return "System is running";
case Status::Suspended: return "System is paused";
default: return "Unknown status";
}
}, s);

std::cout << "Status: " << description << std::endl;
// Output: Status: System is paused
return 0;
}

Storing Data in Enum-Aware Arrays

The magic_enum::containers::array class is a wrapper around std::array that allows you to use enum values directly as indices. This eliminates the need for manual casting to underlying integer types when accessing elements.

The recommended pattern is to default-construct the magic_enum::containers::array and then assign values to specific enum keys using operator[]. For bounds-checked access that throws std::out_of_range on invalid keys, use the at() method.

#include <iostream>
#include <string>
#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, std::string> labels;

// Assign values using enum keys
labels[Direction::North] = "Heading Up";
labels[Direction::South] = "Heading Down";
labels[Direction::East] = "Heading Right";
labels[Direction::West] = "Heading Left";

// Access values safely
std::cout << "North is: " << labels.at(Direction::North) << std::endl;

assert(labels.size() == 4);
return 0;
}

Managing Unique Enum Collections

For cases where you need to store a unique set of enum values, magic_enum::containers::set provides a memory-efficient implementation using a bitset. It offers an interface similar to std::set, including methods like insert, erase, and contains.

Because it is backed by a bitset, the set container is highly performant for enums with a small to medium number of elements, as membership checks are reduced to simple bitwise operations.

#include <iostream>
#include <cassert>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>

enum class Permission { Read, Write, Execute, Delete };

int main() {
magic_enum::containers::set<Permission> user_perms;

// Add permissions to the set
user_perms.insert(Permission::Read);
user_perms.insert(Permission::Write);

// Check for membership
if (user_perms.contains(Permission::Read)) {
std::cout << "User has Read access" << std::endl;
}

// Remove a permission
user_perms.erase(Permission::Write);

assert(user_perms.size() == 1);
assert(!user_perms.contains(Permission::Write));

return 0;
}