перечисление в строку в современном C ++ 11 / C ++ 14 / C ++ 17 и будущем C ++ 20


354

Вопреки всем другим подобным вопросам, этот вопрос касается использования новых функций C ++.

Прочитав много ответов, я так и не нашел:

  • Элегантный способ использования новых функций C ++ 11 , C ++ 14 или C ++ 17
  • Или что-то готовое к использованию в Boost
  • Еще что-то запланировано на C ++ 20

пример

Пример часто лучше длинного объяснения.
Вы можете скомпилировать и запустить этот фрагмент на Coliru .
( Другой предыдущий пример также доступен)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}

Ограничения

  • Пожалуйста, не бесполезное дублирование других ответов или основных ссылок .
  • Пожалуйста, избегайте раздутых ответов на основе макросов или постарайтесь #defineмаксимально сократить накладные расходы.
  • Пожалуйста, не руководство enum-> stringкартирование.

Приятно иметь

  • enumЗначения поддержки, начинающиеся с числа, отличного от нуля
  • Поддержка отрицательных enumзначений
  • Поддержка фрагментированных enumзначений
  • Поддержка class enum(C ++ 11)
  • Поддержка class enum : <type>любых разрешенных <type>(C ++ 11)
  • Преобразование во время компиляции (не во время выполнения) в строку
    или, по крайней мере, быстрое выполнение во время выполнения (например std::map, не очень хорошая идея ...)
  • constexpr (C ++ 11, затем расслаблено в C ++ 14/17/20)
  • noexcept (C ++ 11)
  • C ++ 17 / C ++ 20 дружественный фрагмент

Одна из возможных идея может быть использование ++ возможности компилятора C для генерации C ++ кода при компиляции времени с использованием мета-программирования трюки на основе variadic template classи constexprфункции ...


4
(возможно, не по теме) посмотрите этот блог, связанный с Qt. woboq.com/blog/reflection-in-cpp-and-qt-moc.html . Описывает возможность замены moc (мета-объектного компилятора) в Qt с помощью отражения C ++ (предлагаемый стандарт).
ibre5041

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
Ecatmur

1
Я лично решил эту проблему, реализовав небольшую служебную библиотеку препроцессора, которая позволяет мне циклически обрабатывать переменные макро-аргументы и выполнять функцию над всеми из них. Я передаю значения перечисления в качестве аргументов макроса и автоматически генерирую перечисление и массив строк через препроцессор. Вы, вероятно, можете сделать это, используя Boost Preprocessor.
Витторио Ромео

2
все должно быть решено с C ++? Это так просто автоматически сгенерировать код для строкового представления, всего несколько строк кода.
Кароли Хорват

2
«Пожалуйста , не дают C ответы на макроуровне основе , если это возможно» хорошо, если вы не готовы ждать C ++ 17 вряд ли что - нибудь полезным, и это не что плохо объявлять перечислений , как DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))если вы должны maintaint генерация макросов ... и imho, помещать такие случаи в язык - это еще один вид взлома вместо более мощного гибрида шаблон / макрос. Мы не должны добавлять все эти полезные примеры использования макросов в язык, чтобы просто сказать, что макросы больше не нужны.
PlasmaHH

Ответы:


43

Библиотека только для заголовков Enum обеспечивает статическое отражение для перечислений (в строку, из строки, итерацию) для C ++ 17.

#include <magic_enum.hpp>

enum Color { RED = 2, BLUE = 4, GREEN = 8 };

Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"

std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
  // color.value() -> Color::GREEN
};

Дополнительные примеры можно найти в домашнем репозитории https://github.com/Neargye/magic_enum .

Где недостаток?

Эта библиотека использует специфический для компилятора хак (на основе __PRETTY_FUNCTION__/ __FUNCSIG__), который работает на Clang> = 5, MSVC> = 15,3 и GCC> = 9.

Значение перечисления должно быть в диапазоне [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX].

  • По умолчанию MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128.

  • Если по умолчанию нужен другой диапазон для всех типов перечислений, переопределите макрос MAGIC_ENUM_RANGE_MINи MAGIC_ENUM_RANGE_MAX.

  • MAGIC_ENUM_RANGE_MINдолжно быть меньше или равно чем 0и должно быть больше чем INT16_MIN.

  • MAGIC_ENUM_RANGE_MAXдолжен быть больше чем 0и должен быть меньше чем INT16_MAX.

  • Если нужен другой диапазон для определенного типа перечисления, добавьте специализацию enum_range для необходимого типа перечисления.

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }

Почему пределы диапазона? Это ограничивает некоторую глубину рекурсии или из-за какого-то линейного поиска во время компиляции?
Эмиль Кормиер

Это потрясающе. Спасибо! Возможно, это даже эффективно, если компилятор достаточно умен, чтобы оценивать constexpr std :: array только один раз. Очень очень хорошо.
Iestyn

87

(Подход библиотеки better_enums )

В текущем C ++ есть способ сделать enum to string следующим образом:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };

Применение:

Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
c._to_string();                                  // string "Green"

for (Channel c : Channel::_values())
    std::cout << c << std::endl;

// And so on...

Все операции могут быть сделаны constexpr. Вы также можете реализовать предложение по отражению в C ++ 17, упомянутое в ответе @ecatmur.

  • Есть только один макрос. Я считаю, что это минимально возможный, потому что препроцессор stringization ( #) является единственным способом преобразовать токен в строку в текущем C ++.
  • Макрос довольно ненавязчив - объявления констант, включая инициализаторы, вставляются во встроенное объявление enum. Это означает, что они имеют тот же синтаксис и значение, что и во встроенном перечислении.
  • Повторение устранено.
  • Реализация является наиболее естественной и полезной как минимум в C ++ 11, благодаря constexpr. Его также можно настроить для работы с C ++ 98+ __VA_ARGS__. Это определенно современный C ++.

Определение макроса несколько связано, поэтому я отвечаю на это несколькими способами.

  • Основная часть этого ответа - реализация, которая, я думаю, подходит для ограничения пространства в StackOverflow.
  • Существует также статья CodeProject, описывающая основы реализации в подробном руководстве. [ Должен ли я переместить его сюда? Я думаю, что это слишком много для SO ответа .
  • Существует полнофункциональная библиотека "Better Enums", которая реализует макрос в одном заголовочном файле. Он также реализует N4428 Query Property Queries , текущую версию предложения по отражению C ++ 17 N4113. Таким образом, по крайней мере для перечислений, объявленных с помощью этого макроса, вы можете получить предлагаемое отражение перечисления C ++ 17 сейчас, в C ++ 11 / C ++ 14.

Расширить этот ответ до функций библиотеки несложно - здесь не осталось ничего «важного». Это, однако, довольно утомительно, и есть проблемы переносимости компилятора.

Отказ от ответственности : я являюсь автором как статьи CodeProject, так и библиотеки.

Вы можете попробовать код из этого ответа , библиотеку и реализацию N4428 онлайн в Wandbox. Документация библиотеки также содержит обзор того, как использовать его как N4428 , что объясняет перечислимую часть этого предложения.


объяснение

Код ниже реализует преобразования между перечислениями и строками. Однако его можно расширить и для других вещей, таких как итерация. Этот ответ оборачивает перечисление в struct. Вы также можете генерировать черты structвместе с перечислением.

Стратегия заключается в том, чтобы создать что-то вроде этого

struct Channel {
    enum _enum : char { __VA_ARGS__ };
    constexpr static const Channel          _values[] = { __VA_ARGS__ };
    constexpr static const char * const     _names[] = { #__VA_ARGS__ };

    static const char* _to_string(Channel v) { /* easy */ }
    constexpr static Channel _from_string(const char *s) { /* easy */ }
};

Проблемы:

  1. В итоге мы получим что-то вроде {Red = 1, Green, Blue}инициализатора для массива значений. Это недопустимый C ++, потому что Redэто не присваиваемое выражение. Это решается путем отливки каждой константы к типу , Tкоторый имеет оператор присваивания, но сбросит назначение: {(T)Red = 1, (T)Green, (T)Blue}.
  2. Точно так же мы закончим {"Red = 1", "Green", "Blue"}как инициализатор для массива имен. Нам нужно будет обрезать " = 1". Мне не известен отличный способ сделать это во время компиляции, поэтому мы отложим это до времени выполнения. В результате _to_stringэтого не будет constexpr, но _from_stringвсе же может быть constexpr, потому что мы можем рассматривать пробелы и знаки равенства как терминаторы при сравнении с необрезанными строками.
  3. Оба вышеперечисленных нуждаются в макросе «сопоставления», который может применять другой макрос к каждому элементу в __VA_ARGS__. Это довольно стандартно. Этот ответ включает в себя простую версию, которая может обрабатывать до 8 элементов.
  4. Если макрос должен быть действительно автономным, ему не нужно объявлять статические данные, для которых требуется отдельное определение. На практике это означает, что массивы нуждаются в специальной обработке. Есть два возможных решения: constexpr(или просто const) массивы в области имен или регулярные массивы в constexprнестатических встроенных функциях. Код в этом ответе предназначен для C ++ 11 и использует прежний подход. Статья CodeProject предназначена для C ++ 98 и использует последнее.

Код

#include <cstddef>      // For size_t.
#include <cstring>      // For strcspn, strncpy.
#include <stdexcept>    // For runtime_error.



// A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
// macro(a) macro(b) macro(c) ...
// The helper macro COUNT(a, b, c, ...) expands to the number of
// arguments, and IDENTITY(x) is needed to control the order of
// expansion of __VA_ARGS__ on Visual C++ compilers.
#define MAP(macro, ...) \
    IDENTITY( \
        APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) \
            (macro, __VA_ARGS__))

#define CHOOSE_MAP_START(count) MAP ## count

#define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))

#define IDENTITY(x) x

#define MAP1(m, x)      m(x)
#define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
#define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
#define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
#define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
#define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
#define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
#define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))

#define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) \
    count

#define COUNT(...) \
    IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))



// The type "T" mentioned above that drops assignment operations.
template <typename U>
struct ignore_assign {
    constexpr explicit ignore_assign(U value) : _value(value) { }
    constexpr operator U() const { return _value; }

    constexpr const ignore_assign& operator =(int dummy) const
        { return *this; }

    U   _value;
};



// Prepends "(ignore_assign<_underlying>)" to each argument.
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
    IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))

// Stringizes each argument.
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))



// Some helpers needed for _from_string.
constexpr const char    terminators[] = " =\t\r\n";

// The size of terminators includes the implicit '\0'.
constexpr bool is_terminator(char c, size_t index = 0)
{
    return
        index >= sizeof(terminators) ? false :
        c == terminators[index] ? true :
        is_terminator(c, index + 1);
}

constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                 size_t index = 0)
{
    return
        is_terminator(untrimmed[index]) ? s[index] == '\0' :
        s[index] != untrimmed[index] ? false :
        matches_untrimmed(untrimmed, s, index + 1);
}



// The macro proper.
//
// There are several "simplifications" in this implementation, for the
// sake of brevity. First, we have only one viable option for declaring
// constexpr arrays: at namespace scope. This probably should be done
// two namespaces deep: one namespace that is likely to be unique for
// our little enum "library", then inside it a namespace whose name is
// based on the name of the enum to avoid collisions with other enums.
// I am using only one level of nesting.
//
// Declaring constexpr arrays inside the struct is not viable because
// they will need out-of-line definitions, which will result in
// duplicate symbols when linking. This can be solved with weak
// symbols, but that is compiler- and system-specific. It is not
// possible to declare constexpr arrays as static variables in
// constexpr functions due to the restrictions on such functions.
//
// Note that this prevents the use of this macro anywhere except at
// namespace scope. Ironically, the C++98 version of this, which can
// declare static arrays inside static member functions, is actually
// more flexible in this regard. It is shown in the CodeProject
// article.
//
// Second, for compilation performance reasons, it is best to separate
// the macro into a "parametric" portion, and the portion that depends
// on knowing __VA_ARGS__, and factor the former out into a template.
//
// Third, this code uses a default parameter in _from_string that may
// be better not exposed in the public interface.

#define ENUM(EnumName, Underlying, ...)                               \
namespace data_ ## EnumName {                                         \
    using _underlying = Underlying;                                   \
    enum { __VA_ARGS__ };                                             \
                                                                      \
    constexpr const size_t           _size =                          \
        IDENTITY(COUNT(__VA_ARGS__));                                 \
                                                                      \
    constexpr const _underlying      _values[] =                      \
        { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     \
                                                                      \
    constexpr const char * const     _raw_names[] =                   \
        { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         \
}                                                                     \
                                                                      \
struct EnumName {                                                     \
    using _underlying = Underlying;                                   \
    enum _enum : _underlying { __VA_ARGS__ };                         \
                                                                      \
    const char * _to_string() const                                   \
    {                                                                 \
        for (size_t index = 0; index < data_ ## EnumName::_size;      \
             ++index) {                                               \
                                                                      \
            if (data_ ## EnumName::_values[index] == _value)          \
                return _trimmed_names()[index];                       \
        }                                                             \
                                                                      \
        throw std::runtime_error("invalid value");                    \
    }                                                                 \
                                                                      \
    constexpr static EnumName _from_string(const char *s,             \
                                           size_t index = 0)          \
    {                                                                 \
        return                                                        \
            index >= data_ ## EnumName::_size ?                       \
                    throw std::runtime_error("invalid identifier") :  \
            matches_untrimmed(                                        \
                data_ ## EnumName::_raw_names[index], s) ?            \
                    (EnumName)(_enum)data_ ## EnumName::_values[      \
                                                            index] :  \
            _from_string(s, index + 1);                               \
    }                                                                 \
                                                                      \
    EnumName() = delete;                                              \
    constexpr EnumName(_enum value) : _value(value) { }               \
    constexpr operator _enum() const { return (_enum)_value; }        \
                                                                      \
  private:                                                            \
    _underlying     _value;                                           \
                                                                      \
    static const char * const * _trimmed_names()                      \
    {                                                                 \
        static char     *the_names[data_ ## EnumName::_size];         \
        static bool     initialized = false;                          \
                                                                      \
        if (!initialized) {                                           \
            for (size_t index = 0; index < data_ ## EnumName::_size;  \
                 ++index) {                                           \
                                                                      \
                size_t  length =                                      \
                    std::strcspn(data_ ## EnumName::_raw_names[index],\
                                 terminators);                        \
                                                                      \
                the_names[index] = new char[length + 1];              \
                                                                      \
                std::strncpy(the_names[index],                        \
                             data_ ## EnumName::_raw_names[index],    \
                             length);                                 \
                the_names[index][length] = '\0';                      \
            }                                                         \
                                                                      \
            initialized = true;                                       \
        }                                                             \
                                                                      \
        return the_names;                                             \
    }                                                                 \
};

а также

// The code above was a "header file". This is a program that uses it.
#include <iostream>
#include "the_file_above.h"

ENUM(Channel, char, Red = 1, Green, Blue)

constexpr Channel   channel = Channel::_from_string("Red");

int main()
{
    std::cout << channel._to_string() << std::endl;

    switch (channel) {
        case Channel::Red:   return 0;
        case Channel::Green: return 1;
        case Channel::Blue:  return 2;
    }
}

static_assert(sizeof(Channel) == sizeof(char), "");

Программа выше печатает Red, как и следовало ожидать. Существует определенная степень безопасности типов, поскольку вы не можете создать перечисление без его инициализации, и удаление одного из случаев из этого switchприведет к предупреждению от компилятора (в зависимости от вашего компилятора и флагов). Также обратите внимание, что "Red"во время компиляции был преобразован в enum.


Эй, @mrhthepie, извини, что твое редактирование было отклонено. Я только что видел письмо об этом. Я собираюсь включить это в ответ - спасибо за исправление!
Антрон

это замечательно. Это также сработало бы, если бы я хотел перечислить биты? Как я хочу перечисление BitFlags, каждый из которых 1Uсдвинут на какую-то сумму?
user3240688

1
Кажется, _trimmed_names()в коде, который вы разместили здесь, есть утечка памяти ( new char[length + 1]но вы не установили initializedзначение true). я что-то пропустил? я не вижу той же проблемы в вашем коде GitHub.
user3240688

1
Он установлен на true, но за пределами ifветви (утечка памяти, первоначально обнаруженная @mrhthepie). Нужно переместить его внутрь ... Редактирование. Спасибо за внимательное рассмотрение как этого, так и кода GH.
Antron

1
to_stringможет вернуть string_viewиз C ++ 17, который не требует нулевого завершения, и стать constexpr.
Якк - Адам Невраумонт

74

Для C ++ 17 C ++ 20 вас заинтересует работа Reflection Study Group (SG7). Существует параллельная серия статей, охватывающих формулировку ( P0194 ) и обоснование, дизайн и развитие ( P0385 ). (Ссылки на последние статьи в каждой серии.)

Начиная с P0194r2 (2016-10-15), синтаксис будет использовать предложенное reflexprключевое слово:

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >

Например (адаптировано из reflexpr из ветви Matus Choclik ):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}

Статическое отражение не смогло войти в C ++ 17 (скорее, в окончательный вариант, представленный на совещании по стандартам в ноябре 2016 года в Иссакуа), но есть уверенность, что оно войдет в C ++ 20; из отчета Херба Саттера :

В частности, исследовательская группа Reflection рассмотрела последнее объединенное предложение по статическому отражению и обнаружила, что готова войти в основные группы Evolution на нашем следующем собрании, чтобы приступить к рассмотрению предложения по унифицированному статическому отражению для TS или для следующего стандарта.


2
@antron извините, ваше редактирование отклонено; Я бы одобрил это, если бы видел вовремя. Я не видел N4428, так что спасибо, что поднял головы.
Ecatmur

3
Нет проблем, спасибо за включение. Я отчасти удивляюсь, почему это было отклонено. Я вижу причину «не делает ее более точной», но она явно более точна на сегодняшний день.
Антрон

1
Спасибо :-) Я разбил последний пример, чтобы избежать горизонтальной полосы прокрутки. Как жаль, что значение MyEnum::AAAне может быть передано в качестве второго аргумента std::meta::get_enumerators_m: - /
olibre

1
Тот факт, что такая концептуально простая задача требует 3-х уровней вложенных шаблонных аргументов, очень сложен. Я уверен, что для этого есть конкретные технические причины. Но это не значит, что конечный результат удобен для пользователя. Я люблю C ++, и код имеет для меня смысл. Но 90% других программистов, с которыми я ежедневно работаю, избегают C ++ из-за такого кода. Я разочарован тем, что не видел более простых встроенных решений.
void.pointer

2
Похоже, что текущая оценка для включения предстоящего Reflection TS в стандарт C ++ 23 : herbutter.com/2018/04/02/…
Тим Рей

25

Это похоже на Юрия Финкельштейна; но не требуется повышение. Я использую карту, чтобы вы могли назначать любые значения перечислениям в любом порядке.

Объявление перечислимого класса как:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

Следующий код автоматически создаст класс enum и перегрузку:

  • '+' '+ =' для std :: string
  • << для потоков
  • '~' просто для преобразования в строку (подойдет любой унарный оператор, но лично мне не нравится это для ясности)
  • '*' чтобы получить количество перечислений

Не требуется повышение, все необходимые функции предоставляются.

Код:

#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>

#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())

std::vector<std::string> splitString(std::string str, char sep = ',') {
    std::vector<std::string> vecString;
    std::string item;

    std::stringstream stringStream(str);

    while (std::getline(stringStream, item, sep))
    {
        vecString.push_back(item);
    }

    return vecString;
}

#define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     \
    enum class E : T                                                                                          \
    {                                                                                                         \
        __VA_ARGS__                                                                                           \
    };                                                                                                        \
    std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    \
    std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     \
    {                                                                                                         \
        os << E##MapName[static_cast<T>(enumTmp)];                                                            \
        return os;                                                                                            \
    }                                                                                                         \
    size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 \
    std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          \
    std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
    std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
    std::string &operator+=(std::string &str, E enumTmp)                                                      \
    {                                                                                                         \
        str += E##MapName[static_cast<T>(enumTmp)];                                                           \
        return str;                                                                                           \
    }                                                                                                         \
    E operator++(E &enumTmp)                                                                                  \
    {                                                                                                         \
        auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 \
        if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  \
            iter = E##MapName.begin();                                                                        \
        else                                                                                                  \
        {                                                                                                     \
            ++iter;                                                                                           \
        }                                                                                                     \
        enumTmp = static_cast<E>(iter->first);                                                                \
        return enumTmp;                                                                                       \
    }                                                                                                         \
    bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }

#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
    STRING_REMOVE_CHAR(strMap, ' ');
    STRING_REMOVE_CHAR(strMap, '(');

    std::vector<std::string> enumTokens(splitString(strMap));
    std::map<T, std::string> retMap;
    T inxMap;

    inxMap = 0;
    for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
    {
        // Token: [EnumName | EnumName=EnumValue]
        std::string enumName;
        T enumValue;
        if (iter->find('=') == std::string::npos)
        {
            enumName = *iter;
        }
        else
        {
            std::vector<std::string> enumNameValue(splitString(*iter, '='));
            enumName = enumNameValue[0];
            //inxMap = static_cast<T>(enumNameValue[1]);
            if (std::is_unsigned<T>::value)
            {
                inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
            }
            else
            {
                inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
            }
        }
        retMap[inxMap++] = enumName;
    }

    return retMap;
}

Пример:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);

int main(void) {
    TestEnumClass first, second;
    first = TestEnumClass::FOUR;
    second = TestEnumClass::TWO;

    std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)

    std::string strOne;
    strOne = ~first;
    std::cout << strOne << std::endl; // FOUR

    std::string strTwo;
    strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
    std::cout << strTwo << std::endl; // Enum-TWOTHREE-test

    std::string strThree("TestEnumClass: ");
    strThree += second;
    std::cout << strThree << std::endl; // TestEnumClass: TWO
    std::cout << "Enum count=" << *first << std::endl;
}

You can run the code here


1
Можем ли мы иметь разрывы строк внутри этого макроопределения?
einpoklum

1
Я добавил перегрузку для того, *чтобы получить счетчик перечислений ... Надеюсь, вы не возражаете :-)
Peter VARGA

1
Есть ли какая-либо причина, по которой в этой реализации используется std::map(O (log (n)) индексация), а не std::unordered_map(O (1) индексация)?
Река Там

1
Кроме того, я думаю, что методы должны быть помечены, inlineчтобы вы могли объявить перечисления в заголовочных файлах, как обычно, не получая ошибок «множественного определения» от компоновщика. (хотя не уверен, что это действительно самое чистое / лучшее решение)
River Tam

1
(извините за спам, но я не могу отредактировать комментарии сегодня) есть другие проблемы с этим, находящимся в файле заголовка. Карту ( E##MapName) необходимо переместить в модуль компиляции, который также имеет доступ к перечислению. Я создал решение, но оно не очень чистое, и мне нужно было получить разрешение, чтобы поделиться им. Сейчас я просто комментирую, чтобы сказать, что нет никакого смысла отмечать встроенные методы без дополнительных функций, необходимых для поддержки использования в заголовочном файле.
Река Там

20

Еще в 2011 году я провел выходные, настраивая решение на основе макросов, и в итоге никогда не использовал его.

Моя текущая процедура состоит в том, чтобы запустить Vim, скопировать перечислители в пустое тело переключателя, запустить новый макрос, преобразовать первый перечислитель в инструкцию case, переместить курсор в начало следующей строки, остановить макрос и сгенерировать оставшийся регистр операторы, запустив макрос на других перечислителях.

Макросы Vim более интересны, чем макросы C ++.

Пример из жизни:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};

Я создам это:

std::ostream& operator<< (std::ostream& os, EtherType ethertype)
{
    switch (ethertype)
    {
        case EtherType::ARP : return os << "ARP" ;
        case EtherType::IPv4: return os << "IPv4";
        case EtherType::VLAN: return os << "VLAN";
        case EtherType::IPv6: return os << "IPv6";
        // omit default case to trigger compiler warning for missing cases
    };
    return os << static_cast<std::uint16_t>(ethertype);
}

И вот как я обхожусь.

Тем не менее, встроенная поддержка перечисления enum была бы намного лучше. Мне очень интересно увидеть результаты рабочей группы по отражению в C ++ 17.

Альтернативный способ сделать это был опубликован @sehe в комментариях .


1
Я делаю именно это. Хотя я обычно использую Surround vim и блокирую выборки по пути
sehe

@sehe Интересно. Я должен взглянуть на «объемное звучание», потому что в настоящее время мне требуется много нажатий клавиш.
StackedCrooked

Здесь все в полном порядке, без макросов (если не .считать): i.imgur.com/gY4ZhBE.gif
сэ

1
Анимированный GIF симпатичен, но трудно сказать, когда он начинается и заканчивается, и как далеко мы продвинулись. ... на самом деле, поцарапайте это, это не мило, это отвлекает. Я говорю, убей это.
einpoklum

Этот подход выбора блоков в vim хорош и все, но почему бы просто не использовать что-то вроде :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/?
Руслан

14

Я не знаю, понравится ли вам это или нет, я не очень доволен этим решением, но это дружественный подход C ++ 14, потому что он использует переменные шаблона и злоупотребляет специализацией шаблона:

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

template<MyEnum> const char MyEnumName[] = "Invalid MyEnum value";
template<> const char MyEnumName<MyEnum::AAA>[] = "AAA";
template<> const char MyEnumName<MyEnum::BBB>[] = "BBB";
template<> const char MyEnumName<MyEnum::CCC>[] = "CCC";

int main()
{
    // Prints "AAA"
    std::cout << MyEnumName<MyEnum::AAA> << '\n';
    // Prints "Invalid MyEnum value"
    std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '\n';
    // Well... in fact it prints "Invalid MyEnum value" for any value
    // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

    return 0;
}

Самое худшее в этом подходе - это боль, которую нужно поддерживать, но также и боль поддерживать некоторые другие подобные подходы, не так ли?

Хорошие моменты об этом подходе:

  • Использование переменных шаблонов (функция C ++ 14)
  • С помощью специализации шаблонов мы можем «обнаружить», когда используется недопустимое значение (но я не уверен, что это может быть полезно вообще).
  • Это выглядит аккуратно.
  • Поиск имени выполняется во время компиляции.

Live example

редактировать

Misterious user673679 ты прав; подход с использованием шаблонов переменных C ++ 14 не обрабатывает случай выполнения, я забыл об этом :(

Но мы все еще можем использовать некоторые современные функции C ++ и переменные шаблоны, а также хитрость с переменными шаблонами для достижения перевода во время выполнения от перечисляемого значения к строке ... это так же утомительно, как и другие, но все же стоит упомянуть.

Давайте начнем использовать псевдоним шаблона для сокращения доступа к карте перечисления в строку:

// enum_map contains pairs of enum value and value string for each enum
// this shortcut allows us to use enum_map<whatever>.
template <typename ENUM>
using enum_map = std::map<ENUM, const std::string>;

// This variable template will create a map for each enum type which is
// instantiated with.
template <typename ENUM>
enum_map<ENUM> enum_values{};

Затем вариационный шаблон обманывает:

template <typename ENUM>
void initialize() {}

template <typename ENUM, typename ... args>
void initialize(const ENUM value, const char *name, args ... tail)
{
    enum_values<ENUM>.emplace(value, name);
    initialize<ENUM>(tail ...);
}

« Лучший трюк » здесь - использование шаблона переменной для карты, которая содержит значения и имена каждой записи enum; эта карта будет одинаковой в каждом модуле перевода и везде будет иметь одно и то же имя, поэтому она довольно проста и аккуратна, если мы вызываем initializeфункцию следующим образом:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);

Мы присваиваем имена каждой MyEnumзаписи и можем использоваться во время выполнения:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';

Но можно улучшить с помощью SFINAE и <<оператора перегрузки :

template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::value>::type>
std::ostream &operator <<(std::ostream &o, const ENUM value)
{
    static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
    auto found = enum_values<ENUM>.find(value);

    return o << (found == enum_values<ENUM>.end() ? Unknown : found->second);
}

С правильной operator <<теперь мы можем использовать перечисление следующим образом:

std::cout << MyEnum::AAA << '\n';

Это также мешает поддерживать и может быть улучшено, но надеюсь, вы поняли идею.

Live example


Это выглядит довольно аккуратно (можно ли просто не определять неспециализированную переменную?). Может быть, я что-то упустил, хотя, как я не понимаю, как это вообще обрабатывает случай выполнения.
user673679

@Paula_plus_plus: Разве вы не должны просто использовать std::arrayвместо громоздкой карты? Это станет предпочтительным только для перечислений, начинающихся с ... что, 2 ^ 10 значений? Возможно, даже больше.
einpoklum

@einpoklum было бы замечательно, если бы мы могли во время выполнения убедиться, сколько элементов enumимеет. К сожалению, мы не можем. И весь смысл карты в том, чтобы просто связать имена со значениями, что std::mapхорошо для этого.
PaperBirdMaster

@Paula_plus_plus: вы уже вызываете initialize()функцию, количество аргументов которой равно числу значений enum, поэтому вы знаете количество значений во время компиляции. Это только конкретное значение, которое вы просите напечатать, которое известно только во время выполнения. Кроме того, даже если вы не знаете этого числа, std :: vector будет быстрее, чем std :: map, опять же, почти во всех реалистичных случаях.
einpoklum

@einpoklum это очень хороший момент, я подумаю об этом, спасибо! Единственное, что меня беспокоит, это то, что std::arrayэто не контейнер значения ключа и, следовательно, не хватает методов поиска; В любом случае, я подумаю.
PaperBirdMaster

7

Если ваш enumвыглядит

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

Вы можете переместить содержимое в enumновый файл:

AAA = -8,
BBB = '8',
CCC = AAA + BBB

И тогда значения могут быть окружены макросом:

// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif

// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)

// clean up
#undef ITEM

Следующим шагом может быть включение элементов в enum:

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

И, наконец, вы можете сгенерировать служебные функции по этому поводу enum:

std::string ToString(MyEnum value)
{
  switch( value )
  {
    #define ITEM(X,Y) case X: return #X;
    #include "enum_definition_file"
  }

  return "";
}

MyEnum FromString(std::string const& value)
{
  static std::map<std::string,MyEnum> converter
  {
    #define ITEM(X,Y) { #X, X },
    #include "enum_definition_file"
  };

  auto it = converter.find(value);
  if( it != converter.end() )
    return it->second;
  else
    throw std::runtime_error("Value is missing");
}

Решение может быть применено к более старым стандартам C ++, и оно не использует современные элементы C ++, но его можно использовать для генерации большого количества кода без особых усилий и обслуживания.


3
Нет необходимости в отдельном файле. Это по сути х-макрос .
HolyBlackCat

@HolyBlackCat Если вы разделите решение на несколько файлов, вы можете повторно использовать значения enum для различных целей
eferion

Я пытаюсь вам сказать, что вы можете сделать то же самое, если поместить список значений в один макрос вместе с определением перечисления в заголовке.
HolyBlackCat

@HolyBlackCat да, я вас понимаю, но я предпочитаю это решение. с другой стороны, это решение можно найти в исходном коде clang, так что я думаю, что это хороший способ решить проблему
eferion

Справедливо. Я полагаю, не следовало бы опускать это, так как он действительно может быть использован. (Прошу прощения за фиктивное редактирование, в противном случае система блокирует мой голос.)
HolyBlackCat

6

У меня была такая же проблема пару дней назад. Я не смог найти ни одного решения C ++ без какой-то странной макро-магии, поэтому я решил написать генератор кода CMake для генерации простых операторов регистра.

Применение:

enum2str_generate(
  PATH          <path to place the files in>
  CLASS_NAME    <name of the class (also prefix for the files)>
  FUNC_NAME     <name of the (static) member function>
  NAMESPACE     <the class will be inside this namespace>
  INCLUDES      <LIST of files where the enums are defined>
  ENUMS         <LIST of enums to process>
  BLACKLIST     <LIST of constants to ignore>
  USE_CONSTEXPR <whether to use constexpr or not (default: off)>
  USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)

Функция ищет включаемые файлы в файловой системе (использует каталоги включения, предоставляемые командой include_directories), считывает их и выполняет некоторые регулярные выражения, чтобы сгенерировать класс и функцию (и).

ПРИМЕЧАНИЕ: constexpr подразумевает встроенный в C ++, поэтому использование опции USE_CONSTEXPR создаст класс только заголовок!

Пример:

./includes/ah:

enum AAA : char { A1, A2 };

typedef enum {
   VAL1          = 0,
   VAL2          = 1,
   VAL3          = 2,
   VAL_FIRST     = VAL1,    // Ignored
   VAL_LAST      = VAL3,    // Ignored
   VAL_DUPLICATE = 1,       // Ignored
   VAL_STRANGE   = VAL2 + 1 // Must be blacklisted
} BBB;

./CMakeLists.txt:

include_directories( ${PROJECT_SOURCE_DIR}/includes ...)

enum2str_generate(
   PATH       "${PROJECT_SOURCE_DIR}"
   CLASS_NAME "enum2Str"
   NAMESPACE  "abc"
   FUNC_NAME  "toStr"
   INCLUDES   "a.h" # WITHOUT directory
   ENUMS      "AAA" "BBB"
   BLACKLIST  "VAL_STRANGE")

Формирует:

./enum2Str.hpp:

/*!
  * \file enum2Str.hpp
  * \warning This is an automatically generated file!
  */

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

#include <string>
#include <a.h>

namespace abc {

class enum2Str {
 public:
   static std::string toStr( AAA _var ) noexcept;
   static std::string toStr( BBB _var ) noexcept;
};

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

/*!
  * \file enum2Str.cpp
  * \warning This is an automatically generated file!
  */

#include "enum2Str.hpp"

namespace abc {

/*!
 * \brief Converts the enum AAA to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( AAA _var ) noexcept {
   switch ( _var ) {
      case A1: return "A1";
      case A2: return "A2";
      default: return "<UNKNOWN>";
   }
}

/*!
 * \brief Converts the enum BBB to a std::string
 * \param _var The enum value to convert
 * \returns _var converted to a std::string
 */
std::string enum2Str::toStr( BBB _var ) noexcept {
   switch ( _var ) {
      case VAL1: return "VAL1";
      case VAL2: return "VAL2";
      case VAL3: return "VAL3";
      default: return "<UNKNOWN>";
   }
}
}

Обновить:

Сценарий теперь также поддерживает перечисления в области (enum class | struct), и я переместил его в отдельное хранилище с некоторыми другими часто используемыми сценариями: https://github.com/mensinda/cmakeBuildTools


Вот Это Да! Очень оригинальная и инновационная идея :-) Надеюсь, у вас хватит смелости обновить ваш генератор, чтобы предоставить версию constexprи noexceptверсию ;-) Я также только что посмотрел ваш проект GitHub ;-) Приветствия
olibre

1
Обновлен генератор. Функции теперь будут всегда constexpr и enum: теперь поддерживается <тип>. Спасибо за звезду :)
Mense

Ссылка не работает ... -.-
Йомен

Ссылка теперь исправлена.
Mense

4

Просто создайте свои перечисления. Написание генератора для этой цели занимает около пяти минут.

Генератор кода на Java и Python, супер легко портировать на любой язык, который вам нравится, включая C ++.

Также супер легко расширить любой функциональностью, которую вы хотите.

Пример ввода:

First = 5
Second
Third = 7
Fourth
Fifth=11

сгенерированный заголовок:

#include <iosfwd>

enum class Hallo
{
    First = 5,
    Second = 6,
    Third = 7,
    Fourth = 8,
    Fifth = 11
};

std::ostream & operator << (std::ostream &, const Hallo&);

сгенерированный файл cpp

#include <ostream>

#include "Hallo.h"

std::ostream & operator << (std::ostream &out, const Hallo&value)
{
    switch(value)
    {
    case Hallo::First:
        out << "First";
        break;
    case Hallo::Second:
        out << "Second";
        break;
    case Hallo::Third:
        out << "Third";
        break;
    case Hallo::Fourth:
        out << "Fourth";
        break;
    case Hallo::Fifth:
        out << "Fifth";
        break;
    default:
        out << "<unknown>";
    }

    return out;
}

И генератор, в очень краткой форме, в качестве шаблона для портирования и расширения. Этот пример кода действительно пытается избежать перезаписи любых файлов, но все равно использует его на свой страх и риск.

package cppgen;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class EnumGenerator
{
    static void fail(String message)
    {
        System.err.println(message);
        System.exit(1);
    }

    static void run(String[] args)
    throws Exception
    {
        Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS);
        Charset charset = Charset.forName("UTF8");
        String tab = "    ";

        if (args.length != 3)
        {
            fail("Required arguments: <enum name> <input file> <output dir>");
        }

        String enumName = args[0];

        File inputFile = new File(args[1]);

        if (inputFile.isFile() == false)
        {
            fail("Not a file: [" + inputFile.getCanonicalPath() + "]");
        }

        File outputDir = new File(args[2]);

        if (outputDir.isDirectory() == false)
        {
            fail("Not a directory: [" + outputDir.getCanonicalPath() + "]");
        }

        File headerFile = new File(outputDir, enumName + ".h");
        File codeFile = new File(outputDir, enumName + ".cpp");

        for (File file : new File[] { headerFile, codeFile })
        {
            if (file.exists())
            {
                fail("Will not overwrite file [" + file.getCanonicalPath() + "]");
            }
        }

        int nextValue = 0;

        Map<String, Integer> fields = new LinkedHashMap<>();

        try
        (
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset));
        )
        {
            while (true)
            {
                String line = reader.readLine();

                if (line == null)
                {
                    break;
                }

                if (line.trim().length() == 0)
                {
                    continue;
                }

                Matcher matcher = pattern.matcher(line);

                if (matcher.matches() == false)
                {
                    fail("Syntax error: [" + line + "]");
                }

                String fieldName = matcher.group(1);

                if (fields.containsKey(fieldName))
                {
                    fail("Double fiend name: " + fieldName);
                }

                String valueString = matcher.group(2);

                if (valueString != null)
                {
                    int value = Integer.parseInt(valueString);

                    if (value < nextValue)
                    {
                        fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName);
                    }

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

        try
        (
            PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset));
            PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset));
        )
        {
            headerWriter.println();
            headerWriter.println("#include <iosfwd>");
            headerWriter.println();
            headerWriter.println("enum class " + enumName);
            headerWriter.println('{');
            boolean first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                if (first == false)
                {
                    headerWriter.println(",");
                }

                headerWriter.print(tab + entry.getKey() + " = " + entry.getValue());

                first = false;
            }
            if (first == false)
            {
                headerWriter.println();
            }
            headerWriter.println("};");
            headerWriter.println();
            headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);");
            headerWriter.println();

            codeWriter.println();
            codeWriter.println("#include <ostream>");
            codeWriter.println();
            codeWriter.println("#include \"" + enumName + ".h\"");
            codeWriter.println();
            codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)");
            codeWriter.println('{');
            codeWriter.println(tab + "switch(value)");
            codeWriter.println(tab + '{');
            first = true;
            for (Entry<String, Integer> entry : fields.entrySet())
            {
                codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':');
                codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";");
                codeWriter.println(tab + tab + "break;");

                first = false;
            }
            codeWriter.println(tab + "default:");
            codeWriter.println(tab + tab + "out << \"<unknown>\";");
            codeWriter.println(tab + '}');
            codeWriter.println();
            codeWriter.println(tab + "return out;");
            codeWriter.println('}');
            codeWriter.println();
        }
    }

    public static void main(String[] args)
    {
        try
        {
            run(args);
        }
        catch(Exception exc)
        {
            exc.printStackTrace();
            System.exit(1);
        }
    }
}

И порт для Python 3.5, потому что достаточно отличается, чтобы быть потенциально полезным

import re
import collections
import sys
import io
import os

def fail(*args):
    print(*args)
    exit(1)

pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*')
tab = "    "

if len(sys.argv) != 4:
    n=0
    for arg in sys.argv:
        print("arg", n, ":", arg, " / ", sys.argv[n])
        n += 1
    fail("Required arguments: <enum name> <input file> <output dir>")

enumName = sys.argv[1]

inputFile = sys.argv[2]

if not os.path.isfile(inputFile):
    fail("Not a file: [" + os.path.abspath(inputFile) + "]")

outputDir = sys.argv[3]

if not os.path.isdir(outputDir):
    fail("Not a directory: [" + os.path.abspath(outputDir) + "]")

headerFile = os.path.join(outputDir, enumName + ".h")
codeFile = os.path.join(outputDir, enumName + ".cpp")

for file in [ headerFile, codeFile ]:
    if os.path.exists(file):
        fail("Will not overwrite file [" + os.path.abspath(file) + "]")

nextValue = 0

fields = collections.OrderedDict()

for line in open(inputFile, 'r'):
    line = line.strip()

    if len(line) == 0:
        continue

    match = pattern.match(line)

    if match == None:
        fail("Syntax error: [" + line + "]")

    fieldName = match.group(1)

    if fieldName in fields:
        fail("Double field name: " + fieldName)

    valueString = match.group(2)

    if valueString != None:
        value = int(valueString)

        if value < nextValue:
            fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName)

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

headerWriter = open(headerFile, 'w')
codeWriter = open(codeFile, 'w')

try:
    headerWriter.write("\n")
    headerWriter.write("#include <iosfwd>\n")
    headerWriter.write("\n")
    headerWriter.write("enum class " + enumName + "\n")
    headerWriter.write("{\n")
    first = True
    for fieldName, fieldValue in fields.items():
        if not first:
            headerWriter.write(",\n")

        headerWriter.write(tab + fieldName + " = " + str(fieldValue))

        first = False
    if not first:
        headerWriter.write("\n")
    headerWriter.write("};\n")
    headerWriter.write("\n")
    headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n")
    headerWriter.write("\n")

    codeWriter.write("\n")
    codeWriter.write("#include <ostream>\n")
    codeWriter.write("\n")
    codeWriter.write("#include \"" + enumName + ".h\"\n")
    codeWriter.write("\n")
    codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n")
    codeWriter.write("{\n")
    codeWriter.write(tab + "switch(value)\n")
    codeWriter.write(tab + "{\n")
    for fieldName in fields.keys():
        codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n")
        codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n")
        codeWriter.write(tab + tab + "break;\n")
    codeWriter.write(tab + "default:\n")
    codeWriter.write(tab + tab + "out << \"<unknown>\";\n")
    codeWriter.write(tab + "}\n")
    codeWriter.write("\n")
    codeWriter.write(tab + "return out;\n")
    codeWriter.write("}\n")
    codeWriter.write("\n")
finally:
    headerWriter.close()
    codeWriter.close()

1
Большое спасибо за то, что поделились вашим генератором на двух языках :-) Но есть ли у вас какие-либо идеи, как генерировать во время компиляции? Например, можем ли мы представить перевод вашего генератора с использованием операторов CMake, чтобы обновить сгенерированный C ++ код при изменении входных данных? Я мечтаю заставить компилятор C ++ генерировать перечисления при компиляции с использованием метапрограммирования ( variadic template classи constexprфункций).
olibre

Otoh, в случае, если слишком сложно добавить пользовательскую команду cmake, вы можете автоматизировать свою IDE или вызвать gererator вручную и получить результат в управлении исходным кодом. Иногда все-таки неплохо сгенерировать код в контроле исходного кода, если он не слишком большой, и люди понимают, что они не должны вносить изменения вручную, потому что иногда интересно посмотреть историю сгенерированных файлов, когда вы отлаживаем что-то странное и есть подозрение, что недавнее изменение в генераторе могло что-то сломать :)
yeoman

О генерации вещей во время компиляции в LISP так просто, потому что синтаксис очень прост и понятен. Этому способствует тот факт, что он динамически типизирован, что позволяет ему быть кратким и читаемым без особого синтаксиса. Эквиваленту макросов LISP в C ++ потребуется очень сложный способ описания AST того, что вы пытаетесь сгенерировать. И AST для C ++ никогда не бывает красивым :(
yeoman

Прямо в Make вместо cmake, это очень просто, кстати. Просто сгенерируйте цели .h и .cpp для каждого файла .enum с помощью find, и эти цели зависят от указанных enum defs, поэтому они автоматически перегенерируются при изменении файлов .enum def. Вероятно, в cmake намного проще, потому что он полон магии для такого рода вещей, но я регулярно использую Make, ant и gradle, но имею ограниченное знание Maven, cmake и grunt :)
yeoman

Спасибо за ваш ответ :-) Я думаю, что большинство разработчиков C ++ оценят, сможет ли ваш генератор обнаруживать перечисления непосредственно в коде C ++, например enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};или в нескольких строках: - Как вы думаете, можете ли вы адаптировать свой генератор для обнаружения enumвнутри C ++? файл? Лучшим может быть генерировать код только при обнаружении тега вроде /*<Generate enum to string here>*/. Затем ваш генератор записывает на месте соответствующий сгенерированный код C ++ (заменяя предыдущий сгенерированный код). ^ _ ^ Какой удивительный генератор не так ли? Приветствия :-)
olibre

3

В соответствии с запросом от OP, здесь урезанная версия решения для уродливых макросов на основе Boost Preprosessor и Variadic Macros .

Он допускает простой список, такой как синтаксис элементов перечислителя, наряду с установкой значений для конкретных элементов, чтобы

XXX_ENUM(foo,(a,b,(c,42)));

расширяется до

enum foo {
    a,
    b,
    c=42
};

Наряду с необходимыми функциями для вывода и сделать некоторые преобразования обратно. Этот макрос был здесь уже целую вечность, и я не совсем уверен, что это самый эффективный способ или что он соответствует требованиям, но с тех пор он работал

Полный код можно увидеть в действии как в Ideone, так и в Coliru .

Его гигантское уродство выше; Я бы поставил его под спойлеры, чтобы защитить твои глаза, если бы знал, но я не люблю уценку.

Библиотека (объединена в одном заголовочном файле)

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

Применение

#include "xxx_enum.h"  // the above lib
#include <iostream>

XXX_ENUM(foo,(a,b,(c,42)));

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

Компиляция (скопировать вставить заголовок внутри main.cpp)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

Вывод

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

5
Этот блок кода - это безумное путешествие по удивительным пейзажам метапрограммирования черной магии. Я действительно почувствовал облегчение при достижении main- Дом, милый дом!
Квентин

Просто добавили ссылку на coliru для проверки вывода (есть некоторые предупреждения, нажмите на ссылку в вашем ответе). Я также разделился на Lib / Usage. namespace xxxМожет ли материал быть перемещен в место заголовка? В начале вы можете сказать, что используете, boost/preprocessor.hppи поэтому ответ - современный C ++-совместимый . Пожалуйста, исправьте предупреждения и немного почистите исходный код для лучшего качества.
olibre

@olibre: это copypastad из 5 разных заголовков в нашей библиотеке. Enum_cast из другой более общей части, но я подумал добавить ее тоже, чтобы увидеть, для чего предназначен do_enum_cast в макросе. Предупреждения только от моего main<tab>vim, включая аргументы, которые я не использую. Я не думаю, что этот код может быть действительно очищен, он просто показывает, что можно сделать, а чего не следует;) и если я изменю его здесь, это уже не код, который я использую в производстве ... это одна из тех хрупких вещей что если это работает, лучше никогда не трогать, так как это может рухнуть так, как никто не мог предсказать.
PlasmaHH

Хорошо, плазма, я вижу, это можно рассматривать как доказательство концепции . Но есть слишком много макросов, чтобы за них проголосовали. Тем не менее, спасибо, что поделились. Приветствия
olibre

Привет Плазма. Я выполнил глубокую очистку исходного кода + завершил компиляцию и запустил вывод. пожалуйста, проверьте мои изменения . Я надеюсь, что это нормально для вас. Ответ более ценен? Однако макропроцессоры все еще ужасны!
Хорошего

2

Следующее решение основано на std::array<std::string,N> для данного перечисления.

Для enumдля std::stringпреобразования мы можем просто бросить перечисление для size_tи поиска строки из массива. Операция O (1) и не требует выделения кучи.

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

Для std::stringдля enumпреобразования мы должны сделать линейный поиск по массиву и отливать индекс массива в enum.

Попробуйте здесь с примерами использования: http://coliru.stacked-crooked.com/a/e4212f93bee65076

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


Спасибо за ваш интересный ответ. Пожалуйста, переделайте ваше предложение, чтобы использовать ваш макрос в классе. См coliru.stacked-crooked.com/a/00d362eba836d04b Кроме того пытаются использовать constexprи noexeptключевые слова , когда это возможно. Приветствия :-)
olibre

Вопрос не уточнил этот реквизит.
FKaria

Вопрос обновлен (см. Пример). Два других требования: (1) тип поддержки enum и (2) значения могут отличаться от последовательности 0, 1, 2 ...
olibre

Я переработал свое решение, чтобы его можно было использовать в классе. Я не понял, как сделать значения, отличные от 0,1,2, .. хотя.
FKaria

Привет FKaria. Большое спасибо за вашу переделку. Я сделал некоторые изменения для поддержки нескольких перечислений в одном классе, а также для поддержки enum class X : Typeформата. Пожалуйста, просмотрите мой вклад: coliru.stacked-crooked.com/a/b02db9190d3491a3 Что вы думаете о моих изменениях? У вас есть идея поддержать значения, установленные в enum? Пример enum E{A=3, B=6, C=A-B};Cheers
olibre

2

Это суть обеспечивает простое сопоставление, основанное на вариабельных шаблонах C ++.

Это упрощенная на C ++ 17 версия карты на основе типов из gist :

#include <cstring> // http://stackoverflow.com/q/24520781

template<typename KeyValue, typename ... RestOfKeyValues>
struct map {
  static constexpr typename KeyValue::key_t get(const char* val) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)  // C++17 if constexpr
      return KeyValue::key; // Returns last element
    else {
      static_assert(KeyValue::val != nullptr,
                  "Only last element may have null name");
      return strcmp(val, KeyValue::val()) 
            ? map<RestOfKeyValues...>::get(val) : KeyValue::key;
    }
  }
  static constexpr const char* get(typename KeyValue::key_t key) noexcept {
    if constexpr (sizeof...(RestOfKeyValues)==0)
      return (KeyValue::val != nullptr) && (key == KeyValue::key)
            ? KeyValue::val() : "";
    else
      return (key == KeyValue::key) 
            ? KeyValue::val() : map<RestOfKeyValues...>::get(key);
  }
};

template<typename Enum, typename ... KeyValues>
class names {
  typedef map<KeyValues...> Map;
public:
  static constexpr Enum get(const char* nam) noexcept {
    return Map::get(nam);
  }
  static constexpr const char* get(Enum key) noexcept {
    return Map::get(key);
  }
};

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

enum class fasion {
    fancy,
    classic,
    sporty,
    emo,
    __last__ = emo,
    __unknown__ = -1
};

#define NAME(s) static inline constexpr const char* s() noexcept {return #s;}
namespace name {
    NAME(fancy)
    NAME(classic)
    NAME(sporty)
    NAME(emo)
}

template<auto K, const char* (*V)()>  // C++17 template<auto>
struct _ {
    typedef decltype(K) key_t;
    typedef decltype(V) name_t;
    static constexpr key_t  key = K; // enum id value
    static constexpr name_t val = V; // enum id name
};

typedef names<fasion,
    _<fasion::fancy, name::fancy>,
    _<fasion::classic, name::classic>,
    _<fasion::sporty, name::sporty>,
    _<fasion::emo, name::emo>,
    _<fasion::__unknown__, nullptr>
> fasion_names;

map<KeyValues...>Можно использовать в обоих направлениях:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

Этот пример доступен на godbolt.org

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

Результат от gcc-7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

1
Очень интересный способ метапрограммирования. Я попытался немного упростить ответ, чтобы быть автономным (без зависимости от ссылки Gist). Чтобы быть кратким и понятным, я наконец-то отредактировал ваш ответ. Вы все еще согласны с моими изменениями? Приветствия ;-)
olibre

2

Я тоже был разочарован этой проблемой в течение длительного времени, наряду с проблемой правильного преобразования типа в строку. Однако, что касается последней проблемы, я был удивлен решением, объясненным в разделе Возможно ли напечатать тип переменной в стандартном C ++? , используя идею из Могу ли я получить имена типов C ++ способом constexpr? , Используя эту технику, можно получить аналогичную функцию для получения значения enum в виде строки:

#include <iostream>
using namespace std;

class static_string
{
    const char* const p_;
    const std::size_t sz_;

public:
    typedef const char* const_iterator;

    template <std::size_t N>
    constexpr static_string(const char(&a)[N]) noexcept
        : p_(a)
        , sz_(N - 1)
    {}

    constexpr static_string(const char* p, std::size_t N) noexcept
        : p_(p)
        , sz_(N)
    {}

    constexpr const char* data() const noexcept { return p_; }
    constexpr std::size_t size() const noexcept { return sz_; }

    constexpr const_iterator begin() const noexcept { return p_; }
    constexpr const_iterator end()   const noexcept { return p_ + sz_; }

    constexpr char operator[](std::size_t n) const
    {
        return n < sz_ ? p_[n] : throw std::out_of_range("static_string");
    }
};

inline std::ostream& operator<<(std::ostream& os, static_string const& s)
{
    return os.write(s.data(), s.size());
}

/// \brief Get the name of a type
template <class T>
static_string typeName()
{
#ifdef __clang__
    static_string p = __PRETTY_FUNCTION__;
    return static_string(p.data() + 30, p.size() - 30 - 1);
#elif defined(_MSC_VER)
    static_string p = __FUNCSIG__;
    return static_string(p.data() + 37, p.size() - 37 - 7);
#endif

}

namespace details
{
    template <class Enum>
    struct EnumWrapper
    {
        template < Enum enu >
        static static_string name()
        {
#ifdef __clang__
            static_string p = __PRETTY_FUNCTION__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1);
#elif defined(_MSC_VER)
            static_string p = __FUNCSIG__;
            static_string enumType = typeName<Enum>();
            return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7);
#endif
        }
    };
}

/// \brief Get the name of an enum value
template <typename Enum, Enum enu>
static_string enumName()
{
    return details::EnumWrapper<Enum>::template name<enu>();
}

enum class Color
{
    Blue = 0,
    Yellow = 1
};


int main() 
{
    std::cout << "_" << typeName<Color>() << "_"  << std::endl;
    std::cout << "_" << enumName<Color, Color::Blue>() << "_"  << std::endl;
    return 0;
}

Приведенный выше код был протестирован только на Clang (см. Https://ideone.com/je5Quv ) и VS2015, но его следует адаптировать к другим компиляторам, немного поиграв с целочисленными константами. Конечно, он все еще использует макросы под капотом, но, по крайней мере, одному не нужен доступ к реализации enum.


Это не работает с g ++ 6.3.0 и C ++ 14.
einpoklum

Интересно, потому что перечисление может быть объявлено как обычно, без необходимости заключать его в макрос. Хотя мне не нравятся зависимости компилятора и магические константы.
zett42

2

Я взял идею у @antron и реализовал ее по-другому: класса enum .

Эта реализация отвечает всем требованиям, перечисленным в исходном вопросе, но в настоящее время имеет только одно реальное ограничение : она предполагает, что значения перечисления либо не предоставлены, либо, если они предусмотрены, должны начинаться с 0 и увеличиваться последовательно без пробелов.

Это не внутреннее ограничение - просто я не использую специальные значения enum. Если это необходимо, можно заменить поиск векторов традиционной реализацией switch / case.

В решении используется некоторый c ++ 17 для встроенных переменных, но этого можно легко избежать при необходимости. Это также использует повышение: отделка из-за простоты.

Самое главное, что требуется всего 30 строк кода и никаких макросов черной магии. Код ниже. Он должен быть помещен в заголовок и включен в несколько модулей компиляции.

Его можно использовать так же, как было предложено ранее в этой теме:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

Пожалуйста, дайте мне знать, если это полезно и как это можно улучшить в дальнейшем.


#include <boost/algorithm/string.hpp>   
struct EnumSupportBase {
  static std::vector<std::string> split(const std::string s, char delim) {
    std::stringstream ss(s);
    std::string item;
    std::vector<std::string> tokens;
    while (std::getline(ss, item, delim)) {
        auto pos = item.find_first_of ('=');
        if (pos != std::string::npos)
            item.erase (pos);
        boost::trim (item);
        tokens.push_back(item);
    }
    return tokens;
  }
};
#define ENUM(EnumName, Underlying, ...) \
    enum class EnumName : Underlying { __VA_ARGS__, _count }; \
    struct EnumName ## Support : EnumSupportBase { \
        static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
        static constexpr const char* get_name(EnumName enum_value) { \
            int index = (int)enum_value; \
            if (index >= (int)EnumName::_count || index < 0) \
               return "???"; \
            else \
               return _token_names[index].c_str(); \
        } \
    }; \
    inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
        return os << EnumName##Support::get_name(es); \
    } 

2

Пока вы в порядке написания отдельной .h/.cppпары для каждого запрашиваемого перечисления, это решение работает почти с тем же синтаксисом и возможностями, что и обычное перечисление c ++:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if

enum MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = AAA + BBB
};

.cppФайл 3 строки шаблонного:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

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

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
    std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

Код

Это решение требует 2 исходных файла:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>

#define ETRAITS
#define EDECL(x) x

template <class ENUM>
class EnumTraits
{
public:
    static const std::vector<ENUM>& GetValues()
    {
        return values;
    }

    static ENUM GetValue(const char* name)
    {
        auto match = valueMap.find(name);
        return (match == valueMap.end() ? ENUM() : match->second);
    }

    static const char* GetName(ENUM value)
    {
        auto match = nameMap.find(value);
        return (match == nameMap.end() ? nullptr : match->second);
    }

public:
    EnumTraits() = delete;

    using vector_type = std::vector<ENUM>;
    using name_map_type = std::unordered_map<ENUM, const char*>;
    using value_map_type = std::unordered_map<std::string, ENUM>;

private:
    static const vector_type values;
    static const name_map_type nameMap;
    static const value_map_type valueMap;
};

struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

...а также

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;

#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

объяснение

В этой реализации используется тот факт, что фигурный список элементов определения перечисления может также использоваться в качестве фигурного списка инициализатора для инициализации члена класса.

Когда ETRAITSоценивается в контексте EnumTraits.inl, он расширяется до статического определения элемента дляEnumTraits<> класса.

В EDECLмакро преобразования каждого члена перечисления в список Initializer значений , которые впоследствии получают передаваемые в конструктор члена для того , чтобы заполнить данные перечисления.

EnumInitGuardКласс предназначен для потребления перечисления инициализатора значений , а затем разрушаться - оставляя чистый список данных перечислений.

Льготы

  • c++-подобный синтаксис
  • Работает одинаково для обоих enumи enum class(* почти)
  • Работает на enum типов с любым числовым базовым типом
  • Работает на enum типов с автоматическими, явными и фрагментированными значениями инициализатора
  • Работает для массового переименования (сохранены целочисленные ссылки)
  • Всего 5 символов препроцессора (3 глобальных)

* В отличие от enumsинициализаторов вenum class типах, которые ссылаются на другие значения из того же перечисления, эти значения должны быть полностью квалифицированными

Disbenefits

  • Требуется отдельная .h/.cppпара для каждого запрашиваемогоenum
  • Зависит от замысловатости macroи includeмагии
  • Незначительные синтаксические ошибки превращаются в гораздо большие ошибки
  • Определение classили определение области namespaceвидимости нетривиально
  • Нет инициализации времени компиляции

Комментарии

Intellisense будет немного жаловаться на доступ к закрытым членам при открытии EnumTraits.inl, но поскольку расширенные макросы фактически определяют членов класса, это на самом деле не является проблемой.

#ifndef ENUM_INCLUDE_MULTIБлок в верхней части заголовка файла является незначительным раздражением , что, вероятно , может быть сморщенной вниз в макрос или что - то, но это достаточно маленькие , чтобы жить в его текущем размере.

Чтобы объявить перечисление с областью имен, требуется, чтобы перечисление сначала было объявлено вперед в пределах его пространства имен, а затем определено в глобальном пространстве имен. Кроме того, любые инициализаторы перечисления, использующие значения одного и того же перечисления, должны иметь эти значения полностью квалифицированными.

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
    EDECL(AAA) = -8,
    EDECL(BBB) = '8',
    EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

2

Я не уверен, что этот подход уже описан в одном из других ответов (на самом деле это так, см. Ниже). Я сталкивался с проблемой много раз и не нашел решения, в котором не использовались бы запутанные макросы или сторонние библиотеки. Поэтому я решил написать свою собственную версию макроса.

То, что я хочу включить, является эквивалентом

enum class test1 { ONE, TWO = 13, SIX };

std::string toString(const test1& e) { ... }

int main() {
    test1 x;
    std::cout << toString(x) << "\n";
    std::cout << toString(test1::TWO) << "\n";
    std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
    //std::cout << toString(123);// invalid
}

который должен напечатать

ONE
TWO
13

Я не фанат макросов. Однако, если c ++ изначально не поддерживает преобразование перечислений в строки, нужно использовать генерацию кода и / или макросы (и я сомневаюсь, что это произойдет слишком рано). Я использую X-макрос :

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end

#define x_begin inline std::string toString(const x_name& e) { \
                static std::map<x_name,std::string> names = { 
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

Большинство из них - это определение и отмена определения символов, которые пользователь передаст в качестве параметра X-marco через include. Использование так

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
                           x_value(TWO,13) , \
                           x_val(SIX) \
                   x_end
#include "x_enum.h"

Live Demo

Обратите внимание, что я еще не включил выбор базового типа. Пока что мне это не нужно, но его нужно изменить, чтобы включить его.

Только после написания этого я понял, что это довольно похоже на ответ eferions . Может быть, я читал это раньше, и, возможно, это было основным источником вдохновения. Я всегда не понимал X-макросы, пока не написал свой;).


1

Решения, использующие enum внутри класса / struct (struct default с открытыми членами) и перегруженные операторы:

struct Color
{
    enum Enum { RED, GREEN, BLUE };
    Enum e;

    Color() {}
    Color(Enum e) : e(e) {}

    Color operator=(Enum o) { e = o; return *this; }
    Color operator=(Color o) { e = o.e; return *this; }
    bool operator==(Enum o) { return e == o; }
    bool operator==(Color o) { return e == o.e; }
    operator Enum() const { return e; }

    std::string toString() const
    {
        switch (e)
        {
        case Color::RED:
            return "red";
        case Color::GREEN:
            return "green";
        case Color::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
};

Со стороны это выглядит почти так же, как перечисление класса:

Color red;
red = Color::RED;
Color blue = Color::BLUE;

cout << red.toString() << " " << Color::GREEN << " " << blue << endl;

Это выведет «красный 1 2». Вы можете перегрузить <<, чтобы синий выводил строку (хотя это может привести к неоднозначности, поэтому это невозможно), но он не будет работать с Color :: GREEN, поскольку он автоматически не преобразуется в Color.

Цель неявного преобразования в Enum (которое неявно преобразуется в int или заданный тип) состоит в том, чтобы сделать:

Color color;
switch (color) ...

Это работает, но это также означает, что эта работа тоже:

int i = color;

С классом enum это не скомпилируется. Вы должны быть осторожны, если вы перегружаете две функции, принимающие enum и integer, или удаляете неявное преобразование ...

Другое решение будет включать использование фактического класса enum и статических членов:

struct Color
{
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    //same as previous...
};

Возможно, он занимает больше места и занимает больше времени, но вызывает ошибку компиляции для неявных int-преобразований. Я бы использовал это из-за этого!

Хотя, безусловно, это связано с накладными расходами, но я думаю, что это проще и выглядит лучше, чем другой код, который я видел. Существует также потенциал для добавления функциональности, которая может быть ограничена в пределах класса.

Редактировать : это работает, и большинство из них можно скомпилировать перед выполнением:

class Color
{
public:
    enum class Enum { RED, GREEN, BLUE };
    static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

    constexpr Color() : e(Enum::RED) {}
    constexpr Color(Enum e) : e(e) {}

    constexpr bool operator==(Enum o) const { return e == o; }
    constexpr bool operator==(Color o) const { return e == o.e; }
    constexpr operator Enum() const { return e; }

    Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
    Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }

    std::string toString() const
    {
        switch (e)
        {
        case Enum::RED:
            return "red";
        case Enum::GREEN:
            return "green";
        case Enum::BLUE:
            return "blue";
        default:
            return "unknown";
        }
    }
private:
    const Enum e;
};

Это очень интересно :-) Однако ваша текущая версия подразумевает, что вы должны написать материал вручную case Enum::RED: return "red";. Вопрос в том, чтобы автоматизировать этот процесс компилятором (во время компиляции). Идея вопроса заключается в том, чтобы только изменять или добавлять значения enum без необходимости обновления содержимого toString(). Ты видишь? Спасибо
olibre

1

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

#include <vector>
#include <string>
#include <regex>
#include <iterator>

std::vector<std::string> split(const std::string& s, 
                               const std::regex& delim = std::regex(",\\s*"))
{
    using namespace std;
    vector<string> cont;
    copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), 
         regex_token_iterator<string::const_iterator>(),
         back_inserter(cont));
    return cont;
}

#define EnumType(Type, ...)     enum class Type { __VA_ARGS__ }

#define EnumStrings(Type, ...)  static const std::vector<std::string> \
                                Type##Strings = split(#__VA_ARGS__);

#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
                                EnumStrings(Type, __VA_ARGS__)

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

EnumToString(MyEnum, Red, Green, Blue);

Спасибо, Малем, за твою новаторскую идею. Я отредактировал ваш ответ, чтобы улучшить читаемость. Я надеюсь, что вам нравятся мои изменения. Пожалуйста, продолжайте улучшать свой ответ: (1) дополните раздел «Пример использования» чем-то вроде auto name = MyEnumStrings["Red"];- (2) Почему вы используете enum class? - (3) Вы поддерживаете enum class MyEnum : char { Red, Green, Blue };? - (4) Объяснить функцию split()- (5) Вам нужен параметр const std::regex& delim? - (6) Как насчет генерации MyEnumStringsво время компиляции? => Вы можете использовать constexpr? ... Ура :-)
olibre

Мне очень нравится этот подход. Действительно коротко и легко понять.
Антон Холмберг

1

РЕДАКТИРОВАТЬ: проверьте ниже для более новой версии

Как упоминалось выше, N4113 является окончательным решением этого вопроса, но нам придется ждать больше года, чтобы увидеть его выход.

Между тем, если вам нужна такая функция, вам придется прибегнуть к «простым» шаблонам и некоторой магии препроцессора.

счетчик

template<typename T>
class Enum final
{
    const char* m_name;
    const T m_value;
    static T m_counter;

public:
    Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);}

    const T value() const {return m_value;}
    const char* name() const {return m_name;}
};

template<typename T>
T Enum<T>::m_counter = 0;

#define ENUM_TYPE(x)      using Enum = Enum<x>;
#define ENUM_DECL(x,...)  x(#x,##__VA_ARGS__)
#define ENUM(...)         const Enum ENUM_DECL(__VA_ARGS__);

Применение

#include <iostream>

//the initialization order should be correct in all scenarios
namespace Level
{
    ENUM_TYPE(std::uint8)
    ENUM(OFF)
    ENUM(SEVERE)
    ENUM(WARNING)
    ENUM(INFO, 10)
    ENUM(DEBUG)
    ENUM(ALL)
}

namespace Example
{
    ENUM_TYPE(long)
    ENUM(A)
    ENUM(B)
    ENUM(C, 20)
    ENUM(D)
    ENUM(E)
    ENUM(F)
}

int main(int argc, char** argv)
{
    Level::Enum lvl = Level::WARNING;
    Example::Enum ex = Example::C;
    std::cout << lvl.value() << std::endl; //2
    std::cout << ex.value() << std::endl; //20
}

Простое объяснение

Enum<T>::m_counterустанавливается в 0 внутри каждого объявления пространства имен.
( Может кто-нибудь указать мне, где ^^ это поведение ^^ упоминается в стандарте? )
Магия препроцессора автоматизирует объявление перечислителей.

Недостатки

  • Это не настоящий enumтип, поэтому не может быть продан
  • Не может использоваться в случаях переключения

Альтернативное решение

Это жертвует нумерацией строк (не совсем), но может использоваться в случаях переключения .

#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x)      constexpr type x{__LINE__,#x}

template<typename T>
struct Enum final
{
    const T value;
    const char* name;

    constexpr operator const T() const noexcept {return value;}
    constexpr const char* operator&() const noexcept {return name;}
};

описки

#line 0конфликтует с -pedanticGCC и Clang.

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

Либо начните с #line 1и вычтите 1 из __LINE__.
Или не используйте -pedantic.
И хотя мы делаем все возможное, избегайте VC ++ любой ценой, это всегда было шуткой компилятора.

Применение

#include <iostream>

namespace Level
{
    ENUM_TYPE(short);
    #line 0
    ENUM(OFF);
    ENUM(SEVERE);
    ENUM(WARNING);
    #line 10
    ENUM(INFO);
    ENUM(DEBUG);
    ENUM(ALL);
    #line <next line number> //restore the line numbering
};

int main(int argc, char** argv)
{
    std::cout << Level::OFF << std::endl;   // 0
    std::cout << &Level::OFF << std::endl;  // OFF

    std::cout << Level::INFO << std::endl;  // 10
    std::cout << &Level::INFO << std::endl; // INFO

    switch(/* any integer or integer-convertible type */)
    {
    case Level::OFF:
        //...
        break;

    case Level::SEVERE:
        //...
        break;

    //...
    }

    return 0;
}

Реализация и использование в реальной жизни

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Краткий справочник

#line lineno - cppreference.com


0

Я написал библиотеку для решения этой проблемы, все происходит во время компиляции, кроме получения сообщения.

Применение:

Используйте макрос DEF_MSGдля определения макроса и пары сообщений:

DEF_MSG(CODE_OK,   "OK!")
DEF_MSG(CODE_FAIL, "Fail!")

CODE_OKэто макрос для использования, и "OK!"это соответствующее сообщение.

Используйте get_message()или просто gm()чтобы получить сообщение:

get_message(CODE_FAIL);  // will return "Fail!"
gm(CODE_FAIL);           // works exactly the same as above

Используйте, MSG_NUMчтобы узнать, сколько макросов было определено. Это автоматически увеличится, вам не нужно ничего делать.

Предопределенные сообщения:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Проект: libcodemsg


Библиотека не создает дополнительных данных. Все происходит во время компиляции. В message_def.hэто генерирует enumпризванный MSG_CODE; в message_def.c, он генерирует переменную, содержащую все строки в static const char* _g_messages[].

В таком случае библиотека может создать enumтолько одну . Это идеально подходит для возвращаемых значений, например:

MSG_CODE foo(void) {
    return MSG_OK; // or something else
}

MSG_CODE ret = foo();

if (MSG_OK != ret) {
    printf("%s\n", gm(ret););
}

Еще одна вещь, которая мне нравится, это возможность управлять определениями сообщений в разных файлах.


Я нашел решение этого вопроса выглядит намного лучше.


Привет Мэдвин. Спасибо за вашу идею. Но как это работает? Что такое накладные расходы? (ноль накладных расходов или создает дополнительные данные?). Ваше предложение кажется правильным, но, к сожалению, DEF_MSGдля каждого enumзначения необходимо использовать / обновлять / поддерживать одно утверждение : - / И это то, что в идеале мы хотели бы прекратить делать ... Приветствия
olibre

Спасибо за ответ, @olibre. Пожалуйста, проверьте обновленный ответ. Здесь я не вижу накладных расходов, за исключением вызова функции, необходимого для доступа к строкам. DEF_MSGделает enumтесно сопряженным с сообщением, хотя имеет некоторые ограничения.
Madwyn

Спасибо за добавленное объяснение в вашем ответе :-) Ваша библиотека в порядке, но не может использоваться для нескольких перечислений: - / А как насчет поддержки enum class(C ++ 11) ? Вы можете использовать constexprдля ограничения _g_messagesво время выполнения. Поддержка нескольких enumтипов (избегая _g_messages) с помощью метапрограммирования (передача типа {enum-type, enum-value}) или, возможно, шаблонных переменных (C ++ 14) . Я думаю, что ваша библиотека не соответствует (пока?) Требованиям C ++ 11/14/17. Что вы думаете? Приветствия ;-)
olibre

1
Спасибо за продолжение. Я узнал что-то новое сегодня! Переменные класса и шаблона enum выглядят хорошо. Я думаю, что мой ответ был немного "не по теме", так как он был приправлен Си.
Madwyn

0
#define ENUM_MAKE(TYPE, ...) \
        enum class TYPE {__VA_ARGS__};\
        struct Helper_ ## TYPE { \
            static const String& toName(TYPE type) {\
                int index = static_cast<int>(type);\
                return splitStringVec()[index];}\
            static const TYPE toType(const String& name){\
                static std::unordered_map<String,TYPE> typeNameMap;\
                if( typeNameMap.empty() )\
                {\
                    const StringVector& ssVec = splitStringVec();\
                    for (size_t i = 0; i < ssVec.size(); ++i)\
                        typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
                }\
                return typeNameMap[name];}\
            static const StringVector& splitStringVec() {\
                static StringVector typeNameVector;\
                if(typeNameVector.empty()) \
                {\
                    typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
                    for (auto& name : typeNameVector)\
                    {\
                        name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
                        name = String(#TYPE) + "::" + name;\
                    }\
                }\
                return typeNameVector;\
            }\
        };


using String = std::string;
using StringVector = std::vector<String>;

   StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
    {
        StringVector ret;
        // Pre-allocate some space for performance
        ret.reserve(maxSplits ? maxSplits+1 : 10);    // 10 is guessed capacity for most case

        unsigned int numSplits = 0;

        // Use STL methods 
        size_t start, pos;
        start = 0;
        do 
        {
            pos = str.find_first_of(delims, start);
            if (pos == start)
            {
                // Do nothing
                start = pos + 1;
            }
            else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
            {
                // Copy the rest of the string
                ret.push_back( str.substr(start) );
                break;
            }
            else
            {
                // Copy up to delimiter
                ret.push_back( str.substr(start, pos - start) );

                if(preserveDelims)
                {
                    // Sometimes there could be more than one delimiter in a row.
                    // Loop until we don't find any more delims
                    size_t delimStart = pos, delimPos;
                    delimPos = str.find_first_not_of(delims, delimStart);
                    if (delimPos == String::npos)
                    {
                        // Copy the rest of the string
                        ret.push_back( str.substr(delimStart) );
                    }
                    else
                    {
                        ret.push_back( str.substr(delimStart, delimPos - delimStart) );
                    }
                }

                start = pos + 1;
            }
            // parse up to next real data
            start = str.find_first_not_of(delims, start);
            ++numSplits;

        } while (pos != String::npos);



        return ret;
    }

пример

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


    MY_TEST s1 = MY_TEST::MY_1;
    MY_TEST s2 = MY_TEST::MY_2;
    MY_TEST s3 = MY_TEST::MY_3;

    String z1 = Helper_MY_TEST::toName(s1);
    String z2 = Helper_MY_TEST::toName(s2);
    String z3 = Helper_MY_TEST::toName(s3);

    MY_TEST q1 = Helper_MY_TEST::toType(z1);
    MY_TEST q2 = Helper_MY_TEST::toType(z2);
    MY_TEST q3 = Helper_MY_TEST::toType(z3);

автоматически макрос ENUM_MAKE генерирует «класс enum» и вспомогательный класс с «функцией отражения enum».

Чтобы уменьшить количество ошибок, все сразу определяется только одним ENUM_MAKE.

Преимущество этого кода автоматически создается для размышлений и внимательного изучения макрокода, простого для понимания кода. 'enum to string', производительность 'string to enum' - алгоритм O (1).

Недостатком является то, что при первом использовании инициализируется класс-помощник для строкового вектора и карты enum relection. но если вы хотите, вы также будете предварительно инициализированы. -


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

Эй, ребята, извините, я не очень хорошо говорю по-английски.
desperado_98

автоматически макрос ENUM_MAKE генерирует «класс enum» и вспомогательный класс с «функцией отражения enum». / Чтобы уменьшить количество ошибок, все сразу определяется только одним ENUM_MAKE. Преимущество этого кода автоматически создается для размышлений и внимательного изучения макрокода, простого для понимания кода. Производительность 'enum to string', 'string to enum' - это алгоритм O (1). Недостатком является то, что при первом использовании инициализируется класс-помощник для строкового вектора и карты enum relection. но если вы хотите, вы также будете предварительно инициализированы.
desperado_98

Привет desperado_98. Спасибо за ваш вклад. Пожалуйста, отредактируйте свой ответ и вставьте в него свой комментарий. Компилятор может вычислить ваш пример во время компиляции, если вы используете некоторые приемы метапрограммирования и constexpr. Я имею в виду функции toName()и toType()могут быть оценены во время компиляции, а не во время выполнения (во время выполнения). Пожалуйста, примите стиль C ++ 11/14/17 в своем ответе. Приветствия ;-)
olibre

Более того: совместим ли ваш макрос с enum class MyEnum : short { A, B, C };?
olibre

0

Мое решение без использования макроса.

преимущества:

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

недостатки:

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

так что ... до того дня, когда C ++ реализует функциональность C # Enum.Parse, я застряну с этим:

            #include <unordered_map>

            enum class Language
            { unknown, 
                Chinese, 
                English, 
                French, 
                German
                // etc etc
            };

            class Enumerations
            {
            public:
                static void fnInit(void);

                static std::unordered_map <std::wstring, Language> m_Language;
                static std::unordered_map <Language, std::wstring> m_invLanguage;

            private:
                static void fnClear();
                static void fnSetValues(void);
                static void fnInvertValues(void);

                static bool m_init_done;
            };

            std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>();
            std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>();

            void Enumerations::fnInit()
            {
                fnClear();
                fnSetValues();
                fnInvertValues();
            }

            void Enumerations::fnClear()
            {
                m_Language.clear();
                m_invLanguage.clear();
            }

            void Enumerations::fnSetValues(void)
            {   
                m_Language[L"unknown"] = Language::unknown;
                m_Language[L"Chinese"] = Language::Chinese;
                m_Language[L"English"] = Language::English;
                m_Language[L"French"] = Language::French;
                m_Language[L"German"] = Language::German;
                // and more etc etc
            }

            void Enumerations::fnInvertValues(void)
            {
                for (auto it = m_Language.begin(); it != m_Language.end(); it++)
                {
                    m_invLanguage[it->second] = it->first;
                }
            }

            // usage -
            //Language aLanguage = Language::English;
            //wstring sLanguage = Enumerations::m_invLanguage[aLanguage];

            //wstring sLanguage = L"French" ;
            //Language aLanguage = Enumerations::m_Language[sLanguage];

0

Ну, еще один вариант. Типичный случай использования - это когда вам нужны константы для HTTP-глаголов, а также использование значений строковой версии.

Пример:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
    std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
    std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
    std::cout << "no" << std::endl;
  }

}

VERB класс:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};

  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
    // std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

1
Чтобы уменьшить использование памяти, вы можете заменить элемент const std::string textпросто theStrings[v]. Однако возникает вопрос об особенностях C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20, чтобы избежать необходимости писать такой класс вручную: - /
olibre

0

Мой ответ здесь.

Вы можете получить имена значений enum и эти индексы одновременно как deque of string.

Этот метод требует только небольшого копирования и вставки и редактирования.

Полученный результат требует приведения типа из size_t к типу enum-класса, когда вам нужно значение типа enum-класса, но я думаю, что это очень переносимый и мощный способ обработки enum-класса.

enum class myenum
{
  one = 0,
  two,
  three,
};

deque<string> ssplit(const string &_src, boost::regex &_re)
{
  boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
  boost::sregex_token_iterator e;
  deque<string> tokens;
  while (it != e)
    tokens.push_back(*it++);
  return std::move(tokens);
}

int main()
{
  regex re(",");
  deque<string> tokens = ssplit("one,two,three", re);
  for (auto &t : tokens) cout << t << endl;
    getchar();
  return 0;
}

0

Вы можете использовать библиотеку отражений, например, Ponder :

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

0

(Аналог https://stackoverflow.com/a/54967187/2338477 , слегка измененный).

Вот мое собственное решение с минимальным определением магии и поддержкой отдельных заданий enum.

Вот заголовочный файл:

#pragma once
#include <string>
#include <map>
#include <regex>

template <class Enum>
class EnumReflect
{
public:
    static const char* getEnums() { return ""; }
};

//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
    static std::map<std::string, int> enum2int;
    static std::map<int, std::string> int2enum;

    static void EnsureEnumMapReady( const char* enumsInfo )
    {
        if (*enumsInfo == 0 || enum2int.size() != 0 )
            return;

        // Should be called once per each enumeration.
        std::string senumsInfo(enumsInfo);
        std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
        std::smatch sm;
        int value = 0;

        for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
        {
            string enumName = sm[1].str();
            string enumValue = sm[2].str();

            if (enumValue.length() != 0)
                value = atoi(enumValue.c_str());

            enum2int[enumName] = value;
            int2enum[value] = enumName;
        }
    }
};

template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;

template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;


#define DECLARE_ENUM(name, ...)                                         \
    enum name { __VA_ARGS__ };                                          \
    template <>                                                         \
    class EnumReflect<##name>: public EnumReflectBase<##name> {         \
    public:                                                             \
        static const char* getEnums() { return #__VA_ARGS__; }          \
    };




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

        printf( EnumToString(enumValue3).c_str() );

    From string to enumeration:

       enumName value;

       if( !StringToEnum("enumValue4", value) )
            printf("Conversion failed...");
*/

//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& int2enum = EnumReflect<T>::int2enum;
    auto it = int2enum.find(t);

    if (it == int2enum.end())
        return "";

    return it->second;
}

//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
    EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
    auto& enum2int = EnumReflect<T>::enum2int;
    auto it = enum2int.find(enumName);

    if (it == enum2int.end())
        return false;

    t = (T) it->second;
    return true;
}

А вот пример тестового приложения:

DECLARE_ENUM(TestEnum,
    ValueOne,
    ValueTwo,
    ValueThree = 5,
    ValueFour = 7
);

DECLARE_ENUM(TestEnum2,
    ValueOne2 = -1,
    ValueTwo2,
    ValueThree2 = -4,
    ValueFour2
);

void main(void)
{
    string sName1 = EnumToString(ValueOne);
    string sName2 = EnumToString(ValueTwo);
    string sName3 = EnumToString(ValueThree);
    string sName4 = EnumToString(ValueFour);

    TestEnum t1, t2, t3, t4, t5 = ValueOne;
    bool b1 = StringToEnum(sName1.c_str(), t1);
    bool b2 = StringToEnum(sName2.c_str(), t2);
    bool b3 = StringToEnum(sName3.c_str(), t3);
    bool b4 = StringToEnum(sName4.c_str(), t4);
    bool b5 = StringToEnum("Unknown", t5);

    string sName2_1 = EnumToString(ValueOne2);
    string sName2_2 = EnumToString(ValueTwo2);
    string sName2_3 = EnumToString(ValueThree2);
    string sName2_4 = EnumToString(ValueFour2);

    TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
    bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
    bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
    bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
    bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
    bool b2_5 = StringToEnum("Unknown", t2_5);

Обновленная версия того же заголовочного файла будет храниться здесь:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h


-5

Как насчет простой потоковой перегрузки? Вам по-прежнему необходимо поддерживать отображение, если вы не хотите заниматься какой-либо макро-магией, но я считаю, что она чище, чем ваше первоначальное решение.

#include <cstdint>  // for std::uint_fast8_t
#include <array>
#include <string>
#include <iostream>

enum class MyEnum : std::uint_fast8_t {
   AAA,
   BBB,
   CCC,
};

std::ostream& operator<<(std::ostream& str, MyEnum type)
{
    switch(type)
    {
    case MyEnum::AAA: str << "AAA"; break;
    case MyEnum::BBB: str << "BBB"; break;
    case MyEnum::CCC: str << "CCC"; break;
    default: break;
    }
    return str;
}

int main()
{
   std::cout << MyEnum::AAA <<'\n';
}

5
1) это создает еще больше дублирования 2) оно заставляет вас использовать потоки
Karoly Horvath

6
-1 Извините, @dau_sama, но цель всех перечисленных в строке повторяющихся вопросов состоит в том, чтобы избежать обслуживания перечисления enum / string. Если вы считаете, что ваш ответ не соответствует цели, рассмотрите возможность удаления ответа. Удачи в следующем ответе;) Приветствия
olibre

-9

Самый простой способ?
Используйте Ада: Enumeration'Image( Value )делает именно то, что вы хотите. Если вам действительно нужен C ++, вы можете попробовать экспортировать функцию:

Function To_String( Input : Enumeration ) return Interfaces.C.Strings.chars_ptr is
    ( Interfaces.C.Strings.New_String( Enumeration'Image(Input) ) )
    with Export, Convention => C;

4
Как это отвечает на вопрос вообще? В этом вопросе четко говорится о преобразовании перечисления в строку в современном C ++.
Майкл Чой

1
@MichaelChoi - это так, но есть и проблема использования подходящего инструмента для работы: только то, что C ++ является завершающим по времени и, следовательно, может решать все решаемые проблемы, НЕ означает, что решение является быстрым, поддерживаемым или эффективным. Использование языка, который имеет надлежащую / желаемую функциональность, и его экспорт является правильным решением.
Shark8

3
В первом предложении вопроса «этот вопрос касается использования новых возможностей C ++». затем «[я еще не нашел] элегантного способа использования новых функций C ++ 11, C ++ 14 или C ++ 17». Автор явно искал решение на C ++; Вы дали решение в Аде, поэтому вы не ответили на вопрос. Вы предлагаете включить совершенно другую зависимость для решения чего-то, что, вероятно, не входило в сферу вопросов.
Майкл Чой
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.