Creating a string list and an enum list from a C++ macro

后端 未结 8 1275
误落风尘
误落风尘 2020-12-14 08:26

In order to make my code shorter and easier to change I want to replace something like

enum{ E_AAA, E_BBB, E_CCC };
static const char *strings{\"AAA\", \"BBB         


        
相关标签:
8条回答
  • 2020-12-14 08:50

    I am a bit late to the party but here is another suggestion.
    It creates a strongly typed enum class, say MyEnumName and a companion static helper class Enumator<MyEnumName>.
    It's bigger than previous answers as it has more features, e.g. stream operators for conversion from/to string.
    Note that it relies on c++ 14 standard due to the use of index sequence.

    Usage:

    /* One line definition - no redundant info */
    ENUM_DEFINE(WeekDay /*first item is enum name*/, 
        Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday);
    
    /* works seemlessly with streams (good for logging) */
    auto dayOne = WeekDay::Sunday;
    std::cout << "day of week is: " << day_of_week;
    
    /* explicit construction from string using WeekDay_enum companion class*/
    auto dayTwo = Enumator<WeekDay>::fromString("Tuesday");
    
    
    /*Iterate over all enum values using Enumator<WeekDay> companion class*/
    std::cout << "Days of the week are:\n"
    for (auto enumVal : Enumator<WeekDay>::getValues()) {
        std::cout << enumVal << "\n";
    }
    

    Source:

        #include <array>
        #include <string>
        #include <sstream>
        #include <stdexcept>
    
    template<typename E>
    using isEnum = typename std::enable_if<std::is_enum<E>::value>::type;
    
    template<typename E, typename = isEnum<E>>
    constexpr static int enumSize() {
        return 0;
    }
    
    template<typename E, typename = isEnum<E>>
    inline static std::string getEnumStringValues() {
        return "";
    }
    
    
    /*Enum companion class to hold the methods that can't be declared in an enum*/
    template<typename EnumType, isEnum<EnumType>* = nullptr>
    class Enumator
    {
        Enumator() = delete; /* prevents instantiation */
    
    public:
    
        constexpr static int size() {
            return enumSize<EnumType>();
        }
        /* list of all enum values a string */
        static auto const& getValuesStr()
        {
            static std::array<std::string, size()> values;
            if (values[0].empty()) {
                std::string valuesStr = getEnumStringValues<EnumType>();
                std::stringstream ss(valuesStr);
                for (auto& value : values) {
                    std::getline(ss, value, ',');                   
                }
            }
            return values;
        };
    
        /* list of all enum values */
        static auto const& getValues()
        {
            static std::array<EnumType, size()> values{ make_array(std::make_index_sequence<size()>()) };
            return values;
        };
    
        /* To/from string conversion */
        constexpr static std::string const& toString(EnumType arg) {
            return getValuesStr()[static_cast<unsigned>(arg)];
        }
    
        static EnumType fromString(std::string const& val)
        {
            /* Attempt at converting from string value */
            auto const& strValues = getValuesStr();
    
            for (unsigned int i = 0; i < strValues.size(); i++)
            { 
                if (val == strValues[i])
                {
                    return static_cast<EnumType>(i);
                }
            }
            throw std::runtime_error("No matching enum value found for token: " + val);
        }
    
    private:
        /* Helper method to initialize array of enum values */
        template<std::size_t...Idx>
        static auto make_array(std::index_sequence<Idx...>)
        {
            return std::array<EnumType, size()>{{static_cast<EnumType>(Idx)...}};
        }
    };
    
    template<typename EnumType, isEnum<EnumType>* = nullptr>
    inline std::istream& operator>> (std::istream& input, EnumType& arg)
    {
        std::string val;
        input >> val;
        arg = Enumator<EnumType>::fromString(val);
        return input;
    }
    
    template<typename EnumType, isEnum<EnumType>* = nullptr>
    inline std::ostream& operator<< (std::ostream& output, const EnumType& arg)
    {
        return output << Enumator<EnumType>::toString(arg);
    }
    
    #define ENUM_DEFINE(EnumName,...)\
        \
        enum class EnumName;\
        \
        template<>\
        constexpr int enumSize<EnumName>() {\
            /*Trick to get the number of enum members:*/\
            /*dump all the enum values in an array and compute its size */\
            enum EnumName { __VA_ARGS__ }; \
            EnumName enumArray[]{ __VA_ARGS__ }; \
            return sizeof(enumArray) / sizeof(enumArray[0]); \
        }\
        \
        template<>\
        inline std::string getEnumStringValues<EnumName>() { return #__VA_ARGS__; }\
        \
        enum class EnumName : int { __VA_ARGS__ }
    
    0 讨论(0)
  • 2020-12-14 08:58

    For a simple solution, I'd recommend something like X-Macros.

    For a more complex solution that adds several other features (like range checking, enhanced type safety, optional associated data, etc.), there's a proposed (but never finalized) Boost.Enum library.

    0 讨论(0)
  • 2020-12-14 09:03

    One way to do this is with X-Macros, which are basically a way to define a macro which is then used for generating more complex structures than a simple macro easily allows. Here is an example of doing exactly what you are asking.

    0 讨论(0)
  • 2020-12-14 09:07

    You can do it with a bit of macro magic:

    #define FRUITS \
        etype(Unknown), \
        etype(Apple),   \
        etype(Orange),  \
        etype(Banana),  \
        etype(Apricot), \
        etype(Mango)
    
    #define etype(x) F_##x
    
    typedef enum { FRUITS } Fruit;
    
    #undef etype
    #define etype(x) #x
    
    static const char *strFruit[] = { FRUITS };
    

    Here is a test program:

    #include <iostream>
    #include <exception>
    #include <vector>
    
    #define FRUITS \
        etype(Unknown), \
        etype(Apple),   \
        etype(Orange),  \
        etype(Banana),  \
        etype(Apricot), \
        etype(Mango)
    
    #define etype(x) F_##x
    
    typedef enum { FRUITS } Fruit;
    
    #undef etype
    #define etype(x) #x
    
    static const char *strFruit[] = { FRUITS };
    
    const char *enum2str (Fruit f)
    {
        return strFruit[static_cast<int>(f)];
    }
    
    Fruit str2enum (const char *f)
    {
        const int n = sizeof(strFruit) / sizeof(strFruit[0]);
        for (int i = 0; i < n; ++i)
        {
            if (strcmp(strFruit[i], f) == 0)
                return (Fruit) i;
        }
        return F_Unknown;
    }
    
    int main (int argc, char *argv[])
    {
        std::cout << "I like " << enum2str(F_Mango) << std::endl;
        std::cout << "I do not like " << enum2str(F_Banana) << std::endl;
        std::vector<char *> v;
        v.push_back("Apple");
        v.push_back("Mango");
        v.push_back("Tomato");
        for (int i = 0; i < v.size(); ++i)
        {
            const Fruit f = str2enum(v[i]);
            if (f == F_Unknown)
                std::cout << "Is " << v[i] << " a fruit?" << std::endl;
            else
                std::cout << v[i] << " is a fruit" << std::endl;
        }
        return 0;
    }
    

    It outputs:

    I like Mango
    I do not like Banana
    Apple is a fruit
    Mango is a fruit
    Is Tomato a fruit?
    
    0 讨论(0)
  • 2020-12-14 09:07

    Here is a solution with Boost.Preprocessor:

    #include <boost/preprocessor.hpp>
    
    #define DEFINE_ENUM_DECL_VAL(r, name, val) BOOST_PP_CAT(name, BOOST_PP_CAT(_, val))
    #define DEFINE_ENUM_VAL_STR(r, name, val) BOOST_PP_STRINGIZE(val)
    #define DEFINE_ENUM(name, val_seq)                                                 \
      enum name {                                                                      \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(DEFINE_ENUM_DECL_VAL, name, val_seq)) \
      };                                                                               \
      static const char* BOOST_PP_CAT(name, _strings[] = ) {                           \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(DEFINE_ENUM_VAL_STR, name, val_seq)) \
      };
    
    DEFINE_ENUM(E, (AAA)(BBB)(CCC))
    

    (AAA)(BBB)(CCC) is a Boost.Preprocessor sequence of tree elements AAA, BBB and CCC; the macro append the enum name to it's modalities:

    enum E { E_AAA, E_BBB, E_CCC };
    static const char* E_strings[] = { "AAA", "BBB", "CCC" };
    
    0 讨论(0)
  • 2020-12-14 09:07

    One way to handle this is to define a list macro, i.e. something that expands to another macro that is left for the user to define. For example:

    #define MY_LIST MY_ENTRY(AAA) MY_ENTRY(BBB) MY_ENTRY(CCC)
    

    To define the enum:

    #define MY_ENTRY(x) E_##x,
    enum name
    {
      MY_LIST
      NUMBER_OF_ELEMENTS    /* Needed to eat trailing comma (not needed in C99, but in C++) */
    };
    #undef MY_ENTRY
    

    To define the string:

    #define MY_ENTRY(x) #x,
    static const char *strings[] = { MY_LIST };
    #undef MY_ENTRY
    

    Personally, I find this much easier to work with than the X macro, as this does not rely in include-file magic.

    0 讨论(0)
提交回复
热议问题