A C++26 Compile-Time Enum Reflection Library
The Program
#include <iostream>
int main()
{
enum class ObjectId
{
enum0,
enum1,
enum2,
enum3,
enum4,
enum5,
enum6
};
using ObjectIdInfo =
EnumInfo<ObjectId>;
std::cout
<< "Enum size: "
<< ObjectIdInfo::Size()
<< "\n\n";
ObjectId id =
ObjectIdInfo::MakeEnum(
"enum5");
std::cout
<< "enum5 = "
<< static_cast<int>(id)
<< '\n';
std::cout
<< "enum3 = "
<< ObjectIdInfo::ToString(
ObjectId::enum3)
<< '\n';
std::cout
<< "\nList Enums:\n";
ObjectIdInfo::ForEach(
PrintEnumEntry{});
}
The Library
namespace EnumReflection
{
// ============================================================
// ENUM ENTRY
// ============================================================
template<typename Enum>
struct EnumEntry
{
Enum value;
std::string_view name;
};
// ============================================================
// REFLECTION
// ============================================================
template<typename Enum>
constexpr auto GetEnumerators()
{
return std::define_static_array(
std::meta::enumerators_of(
^^Enum));
}
// ============================================================
// ENUM TABLE GENERATION
// ============================================================
template<typename Enum>
consteval auto GenerateEnumTable()
{
constexpr auto enumerators =
GetEnumerators<Enum>();
std::array<
EnumEntry<Enum>,
enumerators.size()> entries{};
size_t index = 0;
template for
(
constexpr auto enumerator :
[: std::meta::reflect_constant_array(
enumerators) :]
)
{
entries[index++] =
{
[:enumerator:],
std::meta::identifier_of(
enumerator)
};
}
return entries;
}
// ============================================================
// ENUM INFORMATION
// ============================================================
template<typename Enum>
struct EnumInfo
{
static constexpr auto entries =
GenerateEnumTable<Enum>();
static constexpr std::string_view ToString(
Enum value,
std::string_view fallback = "none")
{
for(auto&& entry : entries)
{
if(entry.value == value)
return entry.name;
}
return fallback;
}
static constexpr Enum MakeEnum(
std::string_view name,
Enum fallback = Enum{})
{
for(auto&& entry : entries)
{
if(entry.name == name)
return entry.value;
}
return fallback;
}
static constexpr size_t Size()
{
return entries.size();
}
template<typename Function>
static constexpr void ForEach(
Function&& function)
{
for(auto&& entry : entries)
{
function(
entry.value,
entry.name);
}
}
};
}
Tutorial
Traditional C++ enum conversion requires manually maintaining a second data structure:
enum class ObjectId
{
Player,
Enemy,
Coin
};
constexpr std::pair<ObjectId, std::string_view> Names[] =
{
{ ObjectId::Player, "Player" },
{ ObjectId::Enemy, "Enemy" },
{ ObjectId::Coin, "Coin" }
};
The problem is that the enum and the lookup table can become inconsistent. Adding a new enum value requires updating multiple locations.
C++26 reflection removes this duplication by allowing the compiler to inspect the enum declaration itself.
Reflection as the Source of Truth
The enum declaration becomes the only required definition:
enum class ObjectId
{
Player,
Enemy,
Coin
};
The compiler can now discover the enumerators:
std::meta::enumerators_of(
^^ObjectId);
This produces reflection metadata describing each enum constant. The library transforms that metadata into a compile-time lookup table.
The Compile-Time Pipeline
enum declaration
|
v
std::meta::enumerators_of()
|
v
std::define_static_array()
|
v
template for reflection expansion
|
v
std::array<EnumEntry>
|
v
EnumInfo<T>
The important idea is that no runtime registration occurs. The conversion table exists entirely during compilation.
Automatic Enum To String Conversion
Once generated, converting an enum value to text is simple:
auto name =
ObjectIdInfo::ToString(
ObjectId::Player);
The returned value is a compile-time generated
std::string_view.
No allocation is required.
String To Enum Conversion
The reverse operation is also generated automatically:
ObjectId id =
ObjectIdInfo::MakeEnum(
"Player");
This is useful for:
- configuration files
- serialization
- editor tools
- network protocols
- debugging systems
Enumerating All Values
Because the metadata table exists at compile time, iterating over all enum values is trivial:
struct PrintEnumEntry
{
template<typename Enum>
void operator()(
Enum value,
std::string_view name) const
{
std::cout
<< static_cast<int>(value)
<< " -> "
<< name
<< '\n';
}
};
ObjectIdInfo::ForEach(
PrintEnumEntry{});
The visitor is completely generic and works with any enum type.
Why Use A Type Alias?
The template type itself is implementation detail. Applications normally define a convenient alias:
using ObjectIdInfo =
EnumInfo<ObjectId>;
The rest of the program can now use:
ObjectIdInfo::ToString(
ObjectId::Player);
ObjectIdInfo::MakeEnum(
"Enemy");
ObjectIdInfo::ForEach(
visitor);
Advantages
- No macros
- No duplicated enum tables
- No runtime initialization
- No reflection registration code
- Strong type safety
- Compile-time generated metadata
- Works with any enum type
Future Extensions
The same reflection mechanism can be extended with additional metadata:
- custom display names
- serialization names
- editor categories
- flags and bitmask support
- automatic UI generation
C++26 reflection turns enums from simple integer wrappers into fully inspectable compile-time objects. This library uses that capability to provide enum conversion and enumeration without requiring any manual maintenance.