Magic Enum C++: enum to string and back without macros
Static reflection for enums (to string, from string, iteration) for modern C++, work with any enum type without any macro or boilerplate code
At a glance
- What is it?
- Magic Enum is a header-only C++17 library that reflects any enum at compile time. Here is how the header works, how to install it, and where it stops being the right tool.
- Who is it for?
- Adopt Magic Enum if you have C++17 or newer and want enum_name, enum_cast and enum_values over enums you do not control, without adding a macro to every declaration. Skip it if you need reflection of structs, if your enums are generated in an order you cannot control, or if you cannot accept the compiler-specific range constant it relies on.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 5 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The boilerplate Magic Enum removes
Every C++ codebase with a config file, a log line or a wire protocol eventually writes the same two functions by hand: one that turns an enum into a string, and one that parses a string back into an enum. The usual fix is a macro next to the enum declaration, which means the enum and its string table can drift apart, and that enums declared in third-party headers stay unreflected. Magic Enum takes the other route. The README describes it as a header-only C++17 library that provides static reflection for enums and works with any enum type without any macro or boilerplate code. That word "any" is the actual selling point. If a dependency hands you an enum class you cannot edit, you can still call magic_enum::enum_name on a value of it. The library is aimed at application and library authors on C++17 or newer who need name lookup, parsing or iteration and do not want a code generator or a macro in the declaration. It is not a general reflection library. It reflects enums, and the documentation is organized around that single job.
How the header reflects an enum you never annotated
The mechanism is a compile-time scan over the enum's underlying integer range. For a given enum type the library walks candidate values inside that range and keeps the ones the compiler accepts as valid enumerators, which is why the README can offer enum_values<Color>() as a constexpr sequence and enum_count<Color>() as a constexpr size. Names come from the same scan: enum_name returns the spelling the compiler knows, and enum_names<Color>() returns the sequence of those spellings. Parsing runs the scan in reverse. enum_cast<Color>(std::string) returns an optional, and the README shows variants for case-insensitive matching, a custom BinaryPredicate, and a value_or default. Integer input goes through the same optional-returning shape, so enum_cast<Color>(0) yields Color::BLUE for an enum whose BLUE is zero. Enumeration order is not declaration order in the general case; enum_index returns the position of a value inside the reflected sequence, and the README's own example maps Color::BLUE to index 1 for an enum declared RED = -10, BLUE = 0, GREEN = 10. That distinction matters when you persist indices rather than names. The scan has a cost, and the cost is paid at compile time, not at runtime.
Installing Magic Enum and printing your first name
The README lists packages for Conan, Vcpkg, Build2 and a Meson wrap, and the repository ships a CMakeLists.txt, a meson.build, Bazel files and a package.xml, so the install route depends on which of those you already use. The header lives under include/ and is included as magic_enum/magic_enum.hpp. With vcpkg the port is named magic-enum; with Conan the recipe is magic_enum. The README's basic example is the shortest check that the install worked, an enum class with negative and positive values and a single call to enum_name:
#include <magic_enum/magic_enum.hpp>
#include <iostream>
enum class Color { RED = -10, BLUE = 0, GREEN = 10 };
int main() {
Color c1 = Color::RED;
std::cout << magic_enum::enum_name(c1) << std::endl; // RED
return 0;
}Running that binary should print RED. If it does not, the header was found but the enum was not reflected, which usually means the enum's range constant is wrong for your compiler; the repository keeps that discussion in doc/limitations.md. The Dockerfile in the repository root shows the maintainers' own install-and-consume sequence: configure with MAGIC_ENUM_OPT_BUILD_EXAMPLES=OFF and MAGIC_ENUM_OPT_BUILD_TESTS=OFF, install to a prefix, then reconfigure a second build against that prefix with MAGIC_ENUM_OPT_TEST_INSTALLED_VERSION=ON and run ctest.
Where the reflection stops working
The library's own documentation includes a limitations page, and that page is the honest part of the project. Reflection depends on a per-enum range constant that has to be adjusted for some compilers and enum layouts, which is why the maintainers keep it in a separate document rather than in the feature list. Enums with sparse or very large value ranges make the scan expensive at compile time: an enum whose values span a wide integer range forces the compiler through every candidate in that range. Enums with duplicate values are a second trap. If two enumerators share the same underlying integer, name lookup has to pick one, and the README does not promise which. The flag helpers inherit the same constraint from the other direction: enum_flags_name and enum_flags_cast only make sense after you specialize magic_enum::customize::enum_range with is_flags set to true, as the README shows for a Directions enum built from 1, 2, 4 and 8. Use the bitwise operators without that specialization and the library has no way to know your values are flags. Finally, this is enum reflection only. If the problem is reflecting struct fields, serializing arbitrary types, or generating schema from a type, Magic Enum does not address it and you need a different library.
Magic Enum against a code generator like Better Enums
Better Enums solves the same visible problem, enum to string and back, but from the opposite direction. It is a macro-based code generator: you declare the enum through the library's macro, and the macro emits the string table and the conversion functions as ordinary C++ code. That gives you full control over the generated names and the declaration order, and it works on older standards. The price is that every enum you want reflected must be declared through the macro, so enums from third-party headers stay out of reach, and the generated code is part of your build. Magic Enum inverts both properties. Nothing is generated, so an enum from a dependency is reflectable the moment you include the header, but the names you get are the compiler's spellings and the order you get is the reflected value order, not the order you wrote in the file. If your wire format depends on a hand-curated name list, a generator gives you a place to curate it. If your problem is a third-party enum you cannot touch, Magic Enum is the only one of the two that applies. The README also points at a Compiler Explorer link, so you can check the generated assembly for your own enum before deciding.
Maintenance, licence and the cost of upgrading
The last push to the repository was on 2026-09-14, and the most recent release is v0.9.8 from 2026-05-03, following v0.9.7 in 2024-11-13 and v0.9.6 in 2024-08-13. The gap between v0.9.7 and v0.9.8 is roughly eighteen months, so the release cadence is not frequent, and the version numbers are still below 1.0. That is worth weighing if you pin a version: a header-only library with a slow cadence is cheap to upgrade in the good case and awkward in the bad one, because a change to the range-scanning logic can shift what enum_names returns for an enum with unusual values. The licence is MIT, which the repository states in LICENSE and the README badge repeats. In practical terms that permits use in closed-source products provided the copyright notice and permission notice are preserved, but the terms are short and you should read them rather than take a summary as advice. Because the library is header-only, there is no ABI to break on upgrade; the risk sits in compile-time behaviour and in the reflected output, not in a shared object.
Iterating enums in a loop and other first-week uses
The functions people reach for after enum_name are the iteration helpers, and they are the reason the library shows up in validation code and test harnesses. enum_values<Color>() gives a constexpr array of the enumerators, enum_names<Color>() gives the matching string array, and enum_entries<Color>() gives pairs of value and name, which is what you want when building a lookup table or a dropdown list. For runtime dispatch the README offers enum_switch, which passes each enumerator to a callable as a constexpr constant so you can use it in a switch or a constexpr context, and enum_for_each, which does the same over the whole set. Bounds checking is separate: enum_contains answers whether a value, an integer or a string names a real enumerator, and enum_reflected answers whether a value can be reflected at all. The stream operators are opt-in through using magic_enum::iostream_operators::operator<<, and the README attaches a warning to the bitwise operators, noting they are enabled for all enums once you bring the namespace in. That warning is the one place the documentation asks you to be careful, and it is easy to miss when copying an example.
Editorial conclusion
Adopt Magic Enum if you have C++17 or newer and want enum_name, enum_cast and enum_values over enums you do not control, without adding a macro to every declaration. Skip it if you need reflection of structs, if your enums are generated in an order you cannot control, or if you cannot accept the compiler-specific range constant it relies on. Before committing, verify three things on your own toolchain: that the header compiles under your compiler and standard version, that enum_cast handles the exact string spellings your config files use, and that your build system picks up magic_enum through the same route (vcpkg, Conan, Build2, Meson wrap or the CMake install target) that your CI already uses.
Frequently asked questions
What is magic_enum?
It is a header-only C++17 library that provides static reflection for enums: converting an enum value to its name, parsing a string or an integer back to an enum, and iterating the set of enumerators. The README states it works with any enum type without macros or boilerplate code.
How do I get the name of an enum value with magic_enum?
Call magic_enum::enum_name on the value. The README's basic example declares enum class Color { RED = -10, BLUE = 0, GREEN = 10 } and prints RED for Color::RED.
How do I convert a string to an enum with magic_enum?
Use magic_enum::enum_cast<Color>(name), which returns an optional you check with has_value() or unwrap with value_or. The README also shows a case-insensitive overload and an overload taking a BinaryPredicate.
Does magic_enum require macros in the enum declaration?
No. The README describes the library as working with any enum type without any macro or boilerplate code, which is the main difference from macro-based generators such as Better Enums.
How do I use magic_enum with flags?
Specialize magic_enum::customize::enum_range for your enum with is_flags set to true, then use the flag functions such as enum_flags_name, enum_flags_cast and enum_flags_contains. The README's Directions example shows this and warns that the bitwise operators apply to all enums once the namespace is brought in.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/neargye-magic-enum)