veselink1 / refl-cpp

Static reflection for C++17 (compile-time enumeration, attributes, proxies, overloads, template functions, metaprogramming).
https://veselink1.github.io/refl-cpp/md__introduction.html
MIT License
1.05k stars 76 forks source link

Enum reflection #77

Closed W4RH4WK closed 1 year ago

W4RH4WK commented 1 year ago

refl-cpp solves a lot of my issues that originate from C++'s lack of reflection. Really great work!

I am currently struggling a bit with reflecting enum types. While I know about magic_enum, I am not a big fan. Would it be possible to add support for enum types to refl-cpp?

Ideally, I would like to have something along these lines:

enum class Tonemapping { None, OptionA, OptionB };

REFL_ENUM(Tonemapping)
REFL_ENUM_ENTRY(None)
REFL_ENUM_ENTRY(OptionA)
REFL_ENUM_ENTRY(OptionB)
REFL_END

Although combining the enum name and entry name might not be possible with the preprocessor this way...

Anyway, from that I should be able to iterate over the list of registered values, being able to obtain the underlying enum value (0, 1, 2, ...) and a string of the entry ("None", "OptionA", "OptionB", ...). Getting the number of registered entries as a compile-time constant might also be handy.

Note that I am preferring the procedural style here as I would generate the REFL_ENUM_ENTRY calls via macros to keep things in sync.

enum class Tonemapping{
#define IKAROS_ENUM_Tonemapping \
    IKAROS_ENUM_E(None) \
    IKAROS_ENUM_E(OptionA) \
    IKAROS_ENUM_E(OptionB)

#define IKAROS_ENUM_E(_entry) _entry,
    IKAROS_ENUM_Tonemapping
#undef IKAROS_ENUM_E
};

REFL_ENUM(Tonemapping)
#define IKAROS_ENUM_E(_entry) REFL_ENUM_ENTRY(_entry)
    IKAROS_ENUM_Tonemapping
#undef IKAROS_ENUM_E
REFL_END
veselink1 commented 1 year ago

Generally, I consider enum reflection to be out of scope, because there already are excellent libraries out there. I was going to suggest magic_enum just after reading the question title, but you say you know about it.

I haven't used magic_enum personally, but from looking at their readme, I do not see what more we could do in refl-cpp. It would be useful to know what limitations of magic_enum you are having trouble with.

W4RH4WK commented 1 year ago

Okay. I understand.

It's not so much that there is a specific limitation of magic_enum that causes a problem for me. It's just that the approach seems too sub-optimal to me such that I don't want to build on top of it; even though it might actually work perfectly fine in practice.

Anyway, for future reference: I've tried a couple of different approaches to my issue and it appears that a constexpr array of pairs is sufficient for what I need. Reusing the example from above:

enum class ToneMapping {
#define IKAROS_ENUM_ToneMapping \
    IKAROS_ENUM_E(None) \
    IKAROS_ENUM_E(Uncharted2) \
    IKAROS_ENUM_E(ACES)
#define IKAROS_ENUM_E(_entry) _entry,
    IKAROS_ENUM_ToneMapping
#undef IKAROS_ENUM_E
};
constexpr std::array ToneMappingEntries{
#define IKAROS_ENUM_E(_entry) std::pair{ToneMapping::_entry, #_entry},
    IKAROS_ENUM_ToneMapping
#undef IKAROS_ENUM_E
};

This allows one to iterate over all entries, get the number of entries as a compile-time constant, and build more efficient lookup tables from it.

lookup table ```c++ const etl::unordered_map ToneMappingToStringLookup{ ToneMappingEntries.begin(), ToneMappingEntries.end()}; std::string_view to_string(ToneMapping e) { if (auto it = ToneMappingToStringLookup.find(e); it != ToneMappingToStringLookup.end()) { return it->second; } else { return "invalid"; } } const auto ToneMappingFromStringLookup = [] { etl::unordered_map lookup; for (const auto& entry : ToneMappingEntries) { lookup[entry.second] = entry.first; } return lookup; }(); void from_string_impl(std::string_view input, std::optional& result) { if (auto it = ToneMappingFromStringLookup.find(input); it != ToneMappingFromStringLookup.end()) { result = it->second; } else { result.reset(); } } ``` Technically, an array would be sufficient for the _toString_ lookup, but this requires enum values to always be in the range of 0 to count-1.