Skip to main content

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.