Создание списка строк и списка enum из макроса C ++ - PullRequest
19 голосов
/ 03 апреля 2011

Чтобы сделать мой код короче и проще для изменения, я хочу заменить что-то вроде

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

С макросом, например, INIT (AAA, BBB, CCC); но когда я пытаюсь сделать макрос с переменными аргументами и строковыми параметрами, я получаю сообщение об ошибке, поскольку аргументы не объявлены.

Есть идеи, как это сделать?

Ответы [ 8 ]

27 голосов
/ 03 апреля 2011

Вот решение, которое я узнал несколько дней назад. упрощенная версия, которая отвечает на ваш вопрос:

#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\
    enum name { v1, v2, v3, v4, v5, v6, v7};\
    const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};

ENUM_MACRO(Week, Sun, Mon, Tue, Wed, Thu, Fri, Sat);

Но у вас может быть улучшенная версия с вызовом функции , например:

#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\
    enum name { v1, v2, v3, v4, v5, v6, v7};\
    const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};\
    const char *name##ToString(value) { return name##Strings[value]; }

ENUM_MACRO(Week, Sun, Mon, Tue, Wed, Thu, Fri, Sat);

Это будет расти:

  enum Week { Sun, Mon, Tue, Wed, Thu, Fri, Sat}; 
  const char *WeekStrings[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 
  const char *WeekToString(value) { return WeekStrings[value]; };

Вы даже можете использовать смещение для первого элемента, как этот:

#define ENUM_MACRO(name, offset, v1, v2, v3, v4, v5, v6, v7)\
    enum name { v1 =  offset, v2, v3, v4, v5, v6, v7};\
    const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};\
    const char *name##ToString(value) { return name##Strings[value - offset ]; }

ENUM_MACRO(Week, 1, Sun, Mon, Tue, Wed, Thu, Fri, Sat);

Надеюсь, это поможет.

Береги себя, Бек

Справка:

Распечатать вопрос месяца , Куш, ответ Дэнни Варода

13 голосов
/ 02 декабря 2011

Вы можете сделать это с небольшим количеством макрофагов:

#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 };

Вот тестовая программа:

#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;
}

Это выводит:

I like Mango
I do not like Banana
Apple is a fruit
Mango is a fruit
Is Tomato a fruit?
11 голосов
/ 04 июля 2013

Вот мое решение:

#define FRUITS(fruit) \
  fruit(Apple)        \
  fruit(Orange)       \
  fruit(Banana)       

#define CREATE_ENUM(name) \
  F_##name,

#define CREATE_STRINGS(name) \
  #name,

Хитрость в том, что «фрукт» является аргументом макроса «ФРУКТЫ» и будет заменено тем, что вы когда-либо передаете. Например:

FRUITS(CREATE_ENUM)

расширится до этого:

F_Apple, F_Orange, F_Banana, 

Позволяет создать перечисление и массив строк:

enum fruit {
  FRUITS(CREATE_ENUM)
};

const char* fruit_names[] = {
  FRUITS(CREATE_STRINGS)
};
5 голосов
/ 03 апреля 2011

Вот решение с 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) - последовательность Boost.Preprocessor элементов дерева AAA, BBB и CCC; макрос добавляет имя перечисления к его модальности:

enum E { E_AAA, E_BBB, E_CCC };
static const char* E_strings[] = { "AAA", "BBB", "CCC" };
4 голосов
/ 03 апреля 2011

Один из способов сделать это с помощью X-Macros , который в основном является способом определения макроса, который затем используется для генерации более сложных структур, чем простой макрос позволяет легко. Вот пример выполнения именно того, что вы просите.

2 голосов
/ 03 апреля 2011

Одним из способов справиться с этим является определение макроса списка , то есть чего-то, что расширяется до другого макроса, который остается для определения пользователем. Например:

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

Чтобы определить 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

Чтобы определить строку:

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

Лично я считаю, что с этим гораздо проще работать, чем с макросом X, поскольку это не зависит от магии включаемого файла.

1 голос
/ 21 сентября 2017

Я немного опоздал на вечеринку, но вот еще одно предложение.
Он создает строго типизированный класс enum, скажем, MyEnumName и вспомогательный статический вспомогательный класс Enumator<MyEnumName>.
Это больше, чем предыдущие ответы, поскольку у него больше возможностей, например, потоковые операторы для преобразования из / в строку.
Обратите внимание, что он основан на стандарте c ++ 14 из-за использования индексной последовательности.

Использование:

/* 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";
}

Источник:

    #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__ }
1 голос
/ 03 апреля 2011

Для простого решения я бы порекомендовал что-то вроде X-Macros .

Для более сложного решения, которое добавляет несколько других функций (таких как проверка диапазона, повышенная безопасность типов, необязательные связанные данные и т. Д.), Предлагается (но никогда не завершается) библиотека Boost.Enum .

...