9
\$\begingroup\$

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 astd::map instead 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::map altogheter?

    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?

Jamal's user avatar
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
askedJan 6, 2014 at 16:15
Vittorio Romeo's user avatar
\$\endgroup\$
1

1 Answer1

9
\$\begingroup\$

Just a few things to point out:

  • There's no need to useinline yourself. 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 yournamespaces (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.

answeredApr 4, 2014 at 3:09
Jamal's user avatar
\$\endgroup\$
1
  • 1
    \$\begingroup\$I would even recommand to use the namedetails instead ofInternal. It is commonly used to hide implementation details.\$\endgroup\$CommentedApr 4, 2014 at 8:24

You mustlog in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.