Создание списка строк и списка перечислений из макроса C ++

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

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

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

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


person Tiago    schedule 03.04.2011    source источник
comment
Как именно выглядит ваш макрос?   -  person Xeo    schedule 03.04.2011
comment
Взгляните на Boost.Preprocessor, он уродлив (из-за ограничений cpp), но позволяет писать макросы, которые работают с последовательностями.   -  person Begemoth    schedule 03.04.2011
comment
Похоже, вы забыли цитаты при выводе определения strings. Кроме того, не могли бы вы выбрать язык: C или C ++?   -  person Lightness Races in Orbit    schedule 03.04.2011
comment
Это очень полезный вопрос, и он мне помог. Спасибо за вопрос.   -  person Dipan Mehta    schedule 23.06.2016


Ответы (8)


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

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

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

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

Ссылка:

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

person DrBeco    schedule 03.04.2011
comment
Пожалуйста, модератор, снимите флажок CW. Спасибо. - person DrBeco; 03.04.2011
comment
Как мы видим, OP не активен на сайте. Возможно ли (и желательно!), Чтобы модератор принял мой ответ как принятый (если, конечно, кто-то думает, что это имеет значение для этого)? Спасибо за любой комментарий к процедуре. - person DrBeco; 26.09.2014
comment
Никто, кроме автора вопроса, не может принять ответ. Были дискуссии об автоматическом использовании по умолчанию для таких случаев, как это, но «принято» никогда не означало «лучший», это просто ответ, к которому автор вопроса может относиться больше всего. Только автор вопроса знает это наверняка, поэтому мы очень неохотно вносили такие изменения. - person Tim Post♦; 26.09.2014
comment
Хорошо, но этот макрос нельзя использовать повторно, как макросы CREATE_ENUM и CREATE_STRINGS в одном из других ответов. - person Paul Floyd; 11.12.2017
comment
Спасибо, Пол (хорошее название для группы). Это было прокомментировано в 2011/2014 годах, поэтому контекст был другим. Во всяком случае, мне все еще нравится мой ответ. Макросы общего назначения никогда не используются в полную силу. Вы тратите много времени на создание общего макроса только для того, чтобы воссоздать еще один макрос еще лучше при программировании другого программного обеспечения. Лучше простота. Заботиться. - person DrBeco; 15.12.2017

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

#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?
person Scott Smedley    schedule 01.12.2011
comment
Мне это нравится, но я нашел здесь похожий, но более чистый способ: stackoverflow.com/a/238157/599142 - person tr3w; 13.07.2012

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

#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)
};
person indigo    schedule 04.07.2013
comment
Это прекрасно! Я действительно нуждался в этом, но никогда не думал, что кто-нибудь сможет это решить. - person Dipan Mehta; 23.06.2016

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

person Chris Pitman    schedule 03.04.2011
comment
+1 Я как раз писал это, а вы нашли уже существующий пример :) - person digEmAll; 03.04.2011
comment
Плохой пример для X-Macros, поскольку обработчик уже может преобразовывать аргументы в строку; это означает, что весь X (красный, красный) можно заменить на X (красный) с лучшим X. - person UKMonkey; 13.09.2017

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

enum E { E_AAA, E_BBB, E_CCC };
static const char* E_strings[] = { "AAA", "BBB", "CCC" };
person Begemoth    schedule 03.04.2011

Я немного опоздал на вечеринку, но вот еще одно предложение.
Он создает строго типизированный класс перечисления, скажем 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__ }
person gg99    schedule 21.09.2017
comment
Этот пример кода очень полезен и стал хорошим началом. Но обратите внимание, что у него есть несколько недостатков, которые, возможно, необходимо устранить. Во-первых, в текущей форме макрос не может использоваться в пространствах имен или классах. Во-вторых, он не удаляет явно пробелы из строк перечисления. Это не обязательно, но может быть неожиданным. Обе точки можно решить, приложив определенные усилия, что дает красивое и очень гибкое решение. Спасибо @ gg99! - person emmenlau; 29.04.2020

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

#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, поскольку он не зависит от магии включаемых файлов.

person Lindydancer    schedule 03.04.2011
comment
Конечные запятые вполне подходят для перечислений. - person Xeo; 03.04.2011
comment
Я проверил стандарты, в C99 они в порядке (что было для меня новостью - спасибо). Однако они не принимаются ни C89, ни C ++ (1998). - person Lindydancer; 03.04.2011
comment
Вы правы, Comeau Online также отклоняет конечную запятую в строгом режиме C ++ 03. :) Тогда просто проигнорируйте мой предыдущий комментарий. - person Xeo; 03.04.2011
comment
Похоже, вы только что обнаружили проблему с компилятором Comeau, согласно черновику C ++ 03 должен принимать конечную запятую. - person Lindydancer; 03.04.2011
comment
@Lindydancer: C ++ 03 не должен принимать конечную запятую в соответствии с опубликованным стандартом. - person Mike Seymour; 03.04.2011
comment
Это X-Macro, использование включаемых файлов с X-Macros - это просто расширение, позволяющее перемещать MY_LIST в отдельный файл. - person Chris Pitman; 03.04.2011
comment
X-Macro требует, чтобы вы включили файл в точку использования (где strings определен в приведенном выше примере). Представленный выше макрос списка этого не требует. Одним из преимуществ является то, что вы можете разместить определение в его естественном месте, а не в специальном файле заголовка. - person Lindydancer; 03.04.2011

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

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

person Josh Kelley    schedule 03.04.2011