Iterate, dispatch, and store enum values
When you work with enums in C++17, you often need to perform operations across all members, dispatch logic based on a runtime value, or store data associated with each enum key. magic_enum provides specialized utilities and containers to handle these patterns without the boilerplate of manual switch statements or error-prone integer casting.
Iterating with enum_for_each
If you need to execute logic for every value in an enum—such as generating a report or initializing a registry—magic_enum::enum_for_each iterates through the reflected values at compile time. It passes a magic_enum::enum_constant to your callable, which you must invoke to retrieve the actual enum value.
#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;
// enum_for_each applies the lambda to every value of Color.
// The parameter 'val' is a magic_enum::enum_constant.
magic_enum::enum_for_each<Color>([&names](auto val) {
// You must invoke val() to get the enum value for enum_name.
auto name = magic_enum::enum_name(val());
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::make_index_sequence and the reflected values_v to expand the calls. If your lambda returns a value, enum_for_each collects them into a std::array (if all types match) or a std::tuple.
Dispatching with enum_switch
When you have a runtime enum value and need to execute code specific to that value, magic_enum::enum_switch acts as a functional replacement for a switch block. To ensure safety, you should specify an explicit result type (like std::string). If the runtime value is not a valid member of the enum, enum_switch returns a default-constructed instance of that type instead of triggering undefined behavior.
#include <iostream>
#include <string>
#include <cassert>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_switch.hpp>
enum class Color { Red, Green, Blue };
int main() {
auto get_description = [](Color c) -> std::string {
// Specify std::string as the result type for safety.
return magic_enum::enum_switch<std::string>([](auto val) -> std::string {
// val is a magic_enum::enum_constant wrapping the value.
if constexpr (val == Color::Red) {
return "The color of fire";
} else {
return std::string(magic_enum::enum_name(val()));
}
}, c);
};
std::cout << get_description(Color::Red); // Prints: The color of fire
// Invalid enum values return a default-constructed result (empty string).
auto invalid = static_cast<Color>(999);
std::string result = get_description(invalid);
assert(result.empty());
return 0;
}
The magic_enum::enum_switch implementation in magic_enum/magic_enum_switch.hpp performs a linear search or a hash-based jump (if enabled) to find the matching enum_constant at compile time.
Storing data in containers::array
Standard std::array requires you to manually map enum values to integer indices, which breaks if your enum is non-contiguous or starts at a non-zero value. magic_enum::containers::array solves this by using the enum's reflected index for storage. You can default-construct the array and then assign values using the enum members directly as keys.
#include <iostream>
#include <string>
#include <cassert>
#include <magic_enum/magic_enum.hpp>
#include <magic_enum/magic_enum_containers.hpp>
enum class Color { Red = 10, Green = 20, Blue = 30 };
int main() {
// Create an array mapping Color to std::string.
magic_enum::containers::array<Color, std::string> color_labels;
// Assign values using enum keys.
color_labels[Color::Red] = "Stop";
color_labels[Color::Green] = "Go";
color_labels[Color::Blue] = "Caution";
// Access with at() provides bounds checking.
std::cout << color_labels.at(Color::Red); // Prints: Stop
// The size matches the number of enum members, not the underlying values.
assert(color_labels.size() == 3);
return 0;
}
The magic_enum::containers::array class in magic_enum/magic_enum_containers.hpp wraps a std::array<V, enum_count<E>()>. It uses detail::indexing::at(pos) to resolve the enum value to its position in the reflected value list.
Managing collections with containers::set
If you need to track a unique collection of enum values, magic_enum::containers::set provides a std::set-like interface optimized for enums. It uses a bitset internally for efficiency while providing standard iterators and membership checks like contains.
#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> active_colors;
active_colors.insert(Color::Red);
active_colors.insert(Color::Blue);
if (active_colors.contains(Color::Red)) {
std::cout << "Red is active" << std::endl;
}
// Iteration only yields the values actually present in the set.
for (Color c : active_colors) {
std::cout << magic_enum::enum_name(c) << " "; // Prints: Red Blue
}
active_colors.erase(Color::Red);
assert(active_colors.size() == 1);
return 0;
}
The magic_enum::containers::set implementation uses a FilteredIterator to skip bits that are not set, ensuring that iteration only visits the enum values you have inserted. It is defined alongside other containers in magic_enum/magic_enum_containers.hpp.