I adapted Meta Enum for the needs of vgframework with the following goals:
- More template-friendly (but still LLVM-compatible ;))
- Faster compilation times
In my pet engine project, vgframework, I was initially using magic_enum until I realized my program was compiling more than twice as fast if I removed magic_enum (36s vs 1m16s).
So I was looking for an alternative solution, and amongst others I found meta_enum but I wanted to be able use templates to retrieve enum reflection data like this:
debugPrint("MyEnum::A = \"%s\"\n", getEnumString(test::MyEnum::A).c_str());
Compared to original meta_enum, the macro now also declares a traits struct so that we can get the enum_meta from enum typename. In order to be compatible with LLVM that requires the template specialization traits to be declared at global scope, enums defined in namespaces needs to pass the namespace as argument so that the macro can temporarily "exit" the name space to declare the traits. Only one level of fully qualified namespace names is supported.
namespace test
{
vg_enum(test, MyEnumABC,
X = 0,
Y = 1,
Z = 2
);
}
namespace test
{
vg_enum_class(test, MyEnumABC,
uint,
A = 65,
B = 66,
C = 67
);
}
namespace test
{
vg_enum_global(test, MyEnumUVW,
U = 0,
V = 1,
W = 2
);
}
namespace test
{
vg_enum_class_global(test, MyEnumRGBA,
short,
R = 0x000F,
G = 0x00F0,
B = 0x0F00,
A = 0xF000
);
}
Then to get metadata object associated to an enum type we just need to use
MetaEnumTraits<Type>::Meta
This way, it's possible to implement functions that does not require to specify the meta object name.
Returns a std::string with the label of an enum value.
MyEnumABC enumValue = MyEnumABC::C;
[...]
string label = getEnumString(enumValue);
Returns a const char * with the label of an enum value.
MyEnumABC enumValue = MyEnumABC::C;
[...]
const char * label = getEnumCString(enumValue);
Returns the enum value at index n
getEnumValue<MyEnumABC>(0);
const auto count = enumCount<MyEnumABC>();
Returns the number of elements declared in the enum.
Once I replaced magic_enum with meta_enum, compilation times went down from 1m17s to 56s. Not bad, but still far from 36s. So I removed #includes to and <string_view> from the header and used minimal containers mimicing the same features instead.
I also removed a lot of funcs that we generated from the macro to replace them with the template versions that are instanciated when the code actually used them.
Compilation times were now down to 42s for DX12 debug build, on a 7800X3D CPU
Enum reflection | Total compile time | Delta |
---|---|---|
Disabled | 36s | - |
magic_enum | 77s | +113% |
meta_enum | 56s | +55% |
meta_enum_lite | 42s | +16% |
Original Meta Enum README.md following:
Single-header facility for compile time reflection of enums in C++17.
- Automatic string conversion of enum entries
- Tracking of enum size - i.e. member count
- Look up enum entries by index
- Convenience functions for converting between all of the above
- Supports both enum and enum class
- Supports enums nested in types/namespaces/functions
- Keeps track of the C++ code used to declare the enums
- Single header standard C++17 magic.
See compiled code at: https://godbolt.org/z/TaPqPa
- gcc 7.3 - works
- gcc 8.1 - works
- gcc 8.2 - works
- clang 6.0 - works - however broken with current stdlib version on godbolt's compiler explorer
- MSVC pre 2018 (version on godbolt's compiler explorer) - works for typical case, not for complex enum test case
Either make sure the meta_enum.hpp
file is made available in your include paths or just copy it to your project.
#include <meta_enum.hpp>
Use the macro meta_enum
to declare your enums.
meta_enum(Days, int, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday);
//equivalent to:
//enum Days : int { Monday, ...
Or use meta_enum_class
for enum class.
The meta_enum
macro supports assigning custom values to entries as per usual.
meta_enum(MyEnum, uint8_t, First = 0, Second = 1 << 5, Third = myConstexprFunction<int>());
By using meta_enum
to declare MyEnum
, you get the global constexpr object MyEnum_meta
which stores a representation of your enum and is of type MetaEnum
.
Typedefs:
- UnderlyingType - contains the selected underlying integer type for the enum
Members:
- string - contains the full enum declaration as a
string_view
- members - contains representations of all enum members represented as an std::array with MetaEnumMember objects
Members:
- value - The enum value, for example
MyEnum::Second
- name - String representation of the member. For example
"Second"
- string - The whole declaration string. For example
" Second = 1 << 5"
- index - The numerical index of this member. For example
1
A few functions are provided per meta_enum
to ease usage.
For an enum with the name MyEnum
you will get the following:
std::string_view MyEnum_value_to_string(MyEnum)
converts an enum value to a textual representation. Will use the.name
member of the member, or"__INVALID_ENUM_VAL__"
on invalid input.std::optional<MetaEnumMember> MyEnum_meta_from_name(std::string_view)
Accesses the meta object for a member found by name. Returns nullopt on invalid input.std::optional<MetaEnumMember> MyEnum_meta_from_value(MyEnum)
Accesses the meta object for a member found by enum value. Returns nullopt on invalid input.std::optional<MetaEnumMember> MyEnum_meta_from_index(std::string_view)
Accesses the meta object for a member found by enum member index. Returns nullopt on invalid input.
See the file in the repo examples.cpp
Some configurations of certain compilers seem to break down when building this. Specifically there is a problem with clang 6.0 on godbolt which fails to build due to what looks like problems with std::string_view
not being constexpr even though it should be. See: https://godbolt.org/z/Ob9Cnv
There has also been problems when building with the MSVC Pre 2018 hosted @godbolt.org as well when it builds with the complex test case inside of example.cpp. It works however for the typical case. See: https://godbolt.org/z/rIhpfR
meta_enum
uses string parsing to be able to extract the names of each enum entry. This string parsing takes into account that enum declarations can contain pretty complex meta-programming expressions - it's not as simple as counting commas. The algorithm for doing so is somewhat naive and cannot tell apart >>
as the operator and >>
as the end of two nested templates (std::vector<std::vector<int>> for example
). Due to this, nested template angle brackets have to be separated by whitespace, i.e. >>
has to be > >
.
I'm gladly welcoming suggestions for improvements, pull requests, bug reports, comments or whatever. :)