I recently had the need of "reflecting" multipleenum class constructs in order to get their elements' names asstd::string objects or their element count. I came up with a C++11 variadic macro solution:
namespace ssvu{ namespace Internal { inline std::vector<std::string> getSplittedEnumVarArgs(const std::string& mEnumVarArgs) { std::vector<std::string> result; // getSplit returns a collection of substrings split at a certain token // Example: "a,b,c" -> {"a", "b", "c"} // getTrimmedStrLR removes whitespace from the beginning and the end of a string // Example: " abc " -> "abc" for(const auto& s : getSplit(mEnumVarArgs, ',')) result.emplace_back(getTrimmedStrLR(std::string(std::begin(s), find(s, '=')))); return result; } template<typename> struct ReflectedEnumImpl; template<template<typename> class T, typename TEnum> struct ReflectedEnumImpl<T<TEnum>> { using EnumType = T<TEnum>; inline static const std::vector<std::string>& getElementsAsStrings() noexcept { static std::vector<std::string> result(getSplittedEnumVarArgs(EnumType::getEnumString())); return result; } inline static std::size_t getElementCount() noexcept { return getElementsAsStrings().size(); } inline static const std::string& getElementAsString(TEnum mElement) noexcept { // If the user changed the default enum values by using the `= ...' // syntax, this function will return wrong values and possibly // go out of bounds. Maybe this should throw an exception. assert(!contains(EnumType::getEnumString(), '=')); return getElementsAsStrings()[std::size_t(mElement)]; } }; } #define SSVU_REFLECTED_ENUM_DEFINE_MANAGER(mName) template<typename> class mName #define SSVU_REFLECTED_ENUM(mManagerName, mName, mUnderlying, ...) enum class mName : mUnderlying { __VA_ARGS__ }; \ template<> class mManagerName<mName> : public ssvu::Internal::ReflectedEnumImpl<mManagerName<mName>> \ { \ friend ssvu::Internal::ReflectedEnumImpl<mManagerName<mName>>; \ inline static const std::string& getEnumString(){ static std::string result{#__VA_ARGS__}; return result; } \ }}Example usage:
SSVU_REFLECTED_ENUM_DEFINE_MANAGER(ReflectedEnum);SSVU_REFLECTED_ENUM(ReflectedEnum, Colors, int, Red, Yellow, Green);void tests(){ assert(int(Colors::Red) == 0); assert(int(Colors::Yellow) == 1); assert(int(Colors::Green) == 2); assert(ReflectedEnum<Colors>::getElementAsString(Colors::Red) == "Red"); assert(ReflectedEnum<Colors>::getElementAsString(Colors::Yellow) == "Yellow"); assert(ReflectedEnum<Colors>::getElementAsString(Colors::Green) == "Green");}What do you think?
Thoughts/questions:
Consider the case where the user defines custom values for the enum elements:
SSVU_REFLECTED_ENUM(ReflectedEnum, Test, int, A = -2, B = 15, C = 0);Getting element count would still be possible, as it's easy to count variadic macro elements. However, getting an element's name as a string would require using a
std::mapinstead of an array. Should I figure out a way to detect if the enum has custom values? Or should I ditch the array for anstd::mapaltogheter?Or would an alternative syntax be better? Example:
SSVU_REFLECTED_CUSTOM_ENUM(ReflectedEnum, Test, int, A, -2, B, 15, C, 0);Maybe this would be more flexible and easier to work with.
I have macro variadic args iteration facilities in myssvu library. Do you think it's worthwhile figuring out a way to generate the enum string elements array at compile-time with macro metaprogramming? Or is the current solution good enough?
- \$\begingroup\$Abusing the preprocessor is a lot of fun\$\endgroup\$Vittorio Romeo– Vittorio Romeo2014-01-06 18:07:48 +00:00CommentedJan 6, 2014 at 18:07
1 Answer1
Just a few things to point out:
There's no need to use
inlineyourself. For modern compilers, it merely serves as a suggestion, but they can otherwise determine if and when it's really needed. Readthis for more info.You should use consistent naming for your
namespaces (one is lowercase and the other is uppercase). I'dnot use uppercase as it's commonly used for user-defined types.getElementCount(), like the other accessors here, should also returnconst.
- 1\$\begingroup\$I would even recommand to use the name
detailsinstead ofInternal. It is commonly used to hide implementation details.\$\endgroup\$Morwenn– Morwenn2014-04-04 08:24:13 +00:00CommentedApr 4, 2014 at 8:24
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.
