enum to string в современных C ++ 11 / C ++ 14 / C ++ 17 и будущих C ++ 20

В отличие от всех других подобных вопросов, этот вопрос касается использования новых функций 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 ...


person oHo    schedule 03.03.2015    source источник
comment
Вы можете рассмотреть возможность настройки компилятора GCC с помощью MELT через ваше расширение в MELT, определяя builtin_enum_name; но для этого потребуются дни работы.   -  person Basile Starynkevitch    schedule 03.03.2015
comment
(возможно, не по теме) посмотрите этот блог, связанный с Qt. woboq.com/blog/reflection-in-cpp-and- qt-moc.html. Описывает возможность замены Qt moc (мета-объектный компилятор) с помощью отражения C ++ (предлагаемый стандарт).   -  person ibre5041    schedule 03.03.2015
comment
В исследовании рефлексии было несколько интересных статей, которые варьировались от того, что именно вы пытаетесь сделать, до того, что вы пытаетесь сделать, а также отражают остальную часть языка.   -  person Morwenn    schedule 03.03.2015
comment
N4113: std::enumerator::identifier_v<MyEnum, MyEnum::AAA>   -  person ecatmur    schedule 03.03.2015
comment
Я лично решил эту проблему, реализовав небольшую служебную библиотеку препроцессора, которая позволяет мне перебирать аргументы макроса с переменным числом аргументов и выполнять функцию над всеми из них. Я передаю значения перечисления как аргументы макроса и автоматически генерирую перечисление и массив строк через препроцессор. Вероятно, вы также можете сделать это с помощью препроцессора Boost.   -  person Vittorio Romeo    schedule 03.03.2015
comment
все ли нужно решать с помощью C ++? Так легко автоматически сгенерировать код для строкового представления, всего пара строк кода.   -  person Karoly Horvath    schedule 03.03.2015
comment
Привет, @VittorioRomeo. Вы хотите предложить что-то, требующее столько же строк кода, как предлагает Мэттью М.?   -  person oHo    schedule 03.03.2015
comment
@olibre: Да, именно так   -  person Vittorio Romeo    schedule 03.03.2015
comment
Пожалуйста, не предоставляйте ответы на основе макросов C, если возможно, хорошо, если вы не готовы дождаться C ++ 17, вряд ли что-нибудь пригодное для использования, и не это плохо объявить свои перечисления как DEC_ENUM(enumname, (a,b,c,(d,b),(e,42))) если только вам не нужно поддерживать генерирующий макрос ... и imho помещение таких случаев в язык - это всего лишь еще один вид взлома вместо более мощного гибрида шаблона / макроса. Мы не должны добавлять все эти полезные варианты использования макросов в язык только для того, чтобы иметь возможность сказать, что макросы больше не нужны.   -  person PlasmaHH    schedule 03.03.2015
comment
@olibre: может мне нужна анонимная учетная запись, чтобы не получать жалкие голоса против;) Я постараюсь извлечь ее из нашей библиотеки ...   -  person PlasmaHH    schedule 03.03.2015
comment
@olibre: как раз наоборот. Я предпочитаю генерацию кода. это приводит к более чистому коду.   -  person Karoly Horvath    schedule 03.03.2015
comment
@olibre: вы можете использовать c ++ (lol) или, желательно, любой переносимый язык сценариев: ruby, python, perl ... используете ли вы C ++ в качестве ввода (со всеми головными болями синтаксического анализа C ++) и генерируете из него дополнительный код C ++ или используете какой-то другой (предметно-ориентированный) язык в качестве ввода зависит от вас ... Я думаю, что имеет смысл.   -  person Karoly Horvath    schedule 03.03.2015
comment
Привет, @VittorioRomeo. Думаю, ваш код намного лучше, чем решение от PlasmaHH. Вы потратите некоторое время, пытаясь как можно больше сократить накладные расходы на макросы, насколько это возможно ... Дайте мне знать;) Ура   -  person oHo    schedule 04.03.2015
comment
Возможный дубликат Есть ли простой способ конвертировать C ++ enum to string? Ограничение новыми версиями не сильно меняет, поскольку, как только новая версия что-то позволяет, кто-то ответит на старые вопросы. Лучше все сконцентрировать в одном месте ИМХО.   -  person Ciro Santilli 新疆再教育营六四事件ۍ    schedule 11.12.2016
comment
Спасибо @CiroSantilli 烏坎 事件 2016 六四 事件 法轮功 за вашу работу по уборке. Однако Есть ли простой способ преобразовать перечисление C ++ в строку? есть ответ с 2008 года (почти 10 лет назад). Но на этот вопрос пока нет полезного ответа. Этот текущий вопрос не о каком-либо простом способе, а о том, чтобы избежать сложной генерации предварительной сборки или директив препроцессора = ›об окончательной мечте C ++: решение, 100% на C ++ (например, без #define). Вы чувствуете разницу? Что вы посоветуете мне изменить в вопросе, чтобы показать эту разницу? Ваше здоровье   -  person oHo    schedule 12.12.2016
comment
@olibre на этот вопрос на сегодня есть как минимум два пригодных ответа. 1. Хороший ответ @ ecatmur о C ++ 17, который мы не можем редактировать каждый раз, когда в обсуждении C ++ 17 появляется обновление. См. список рассылки группы изучения размышлений. 2. Мой ответ с красивым синтаксисом для текущего C ++, который используется многими людьми в производстве, но использует #define внутри. Вы просите об удобном решении. Сегодняшний правильный ответ заключается в том, что полностью правильное решение будет доступно позже (т.е. пока примите @ecatmur).   -  person antron    schedule 27.12.2016
comment
ОК @antron Я подтверждаю ваш очень интересный ответ. Но когда компилятор реализует экспериментальное отражение C ++, мы обновим ответ ecatmur, чтобы проиллюстрировать некоторые примеры. Тогда его ответ станет действительным. Спасибо за отзыв, Ура ;-)   -  person oHo    schedule 27.12.2016
comment
Я просто оставлю здесь комментарий к этому предложению Herb Sutters на Metaclasses выглядит многообещающим. Вот вводное видео: youtube.com/watch?v=6nsyX37nsRs   -  person nonsensation    schedule 04.08.2017
comment
Я не понимаю последних двух пунктов. Что такое сниппет C ++ 14 / C ++ 17? В каком смысле это может быть C ++ State of the Art?   -  person einpoklum    schedule 11.03.2018
comment
Я имел в виду: (1) сниппет C ++ 14 / C ++ 17 дружественный - ›Предоставьте короткий пример C ++, в котором используются некоторые функции C ++ 14 / C ++ 17 . (2) Современное состояние C ++ - ›Дайте некоторые пояснения о новом способе использования функций C ++ для преобразования перечисления в строку. Я думаю, что два последних пункта на самом деле непонятны. Вы хотите их перефразировать / удалить / объединить / дополнить? Спасибо за ваше последнее издание, вы прояснили вопрос :-) Ура   -  person oHo    schedule 13.03.2018
comment
Пожалуйста, не повторяйте бесценное дублирование других ответов или базовой ссылки. Должно быть бесполезное дублирование, верно?   -  person L. F.    schedule 14.04.2019
comment
std :: map может быть std :: unordered_map   -  person ivan.ukr    schedule 12.06.2019
comment
@olibre каким-то образом автор magic_enum ответил несколько месяцев назад, но принятый ответ от кого-то другого. Imo, было бы справедливо принять исходный ответ автора   -  person Pavel P    schedule 07.09.2019
comment
Большое спасибо @Pavel за ваши глаза, я искал другие ответы о magic_enum, но не заметил, что автор magic_enum ответил, используя другую из своих библиотек. Я временно принял ее / его ответ, но ожидаю, что он / она его завершит - ›stackoverflow.com/a/55312360/938111 < / а>   -  person oHo    schedule 13.09.2019
comment
Есть ли какие-либо предложения по этой языковой функции? Судя по количеству лайков на этот вопрос и количеству подобных вопросов, многие люди хотели бы реализовать эту функцию. Лично я думаю о расширении enum class, чтобы мы могли писать конструкторы (например, создавать перечисление из строки) и служебные методы, такие как очень популярный System.Object.ToString() из C #. Есть мысли по этому поводу?   -  person ワイきんぐ    schedule 28.01.2021


Ответы (30)


Библиотека только для заголовков Magic 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;
    };
    }
    
person Neargye    schedule 23.03.2019
comment
Почему пределы диапазона? Это для ограничения какой-то глубины рекурсии или из-за какого-то линейного поиска во время компиляции? - person Emile Cormier; 11.04.2020
comment
Это потрясающе. Спасибо! Вероятно, это даже эффективно, если компилятор достаточно умен, чтобы оценить constexpr std :: array только один раз. Очень очень хорошо. - person iestyn; 01.05.2020
comment
@EmileCormier Пределы диапазона необходимы, потому что библиотека должна проверять каждое возможное значение в диапазоне, чтобы увидеть, соответствует ли оно перечислителю. Он создает экземпляр шаблона функции is_valid для каждого значения в диапазоне [-128, 127]. Это может привести к длительному времени компиляции, поэтому диапазон по умолчанию довольно консервативен. Вот упрощенная версия метода: godbolt.org/z/GTxfva - person Alastair Harrison; 07.01.2021
comment
для меня самым важным недостатком является то, что он не работает тихо: godbolt.org/z/TTMx1v Есть ограничение от размера значений, но когда ограничение не выполняется, не возникает ни ошибки компиляции, ни исключения, а возвращается только пустая строка. - person MateuszL; 03.02.2021
comment
@Neargye, не будет ли у меня проблем, если у меня есть перечисление со значениями за пределами диапазона мин / макс, но я буду использовать только функцию перечисления в строку? - person acegs; 24.05.2021
comment
@acegs, если значение вне диапазона min / max enum-to-string вернет пустую строку. - person Neargye; 24.05.2021

(подход библиотеки 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.

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

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

  • Основная часть этого ответа - это реализация, которая, как мне кажется, подходит для ограничений пространства в StackOverflow.
  • Также есть статья CodeProject с описанием основ реализации в подробном руководстве. [Переместить сюда? Я думаю, это слишком много для ТАК-ответа].
  • Существует полнофункциональная библиотека «Better Enums», которая реализует макрос в одном файле заголовка. Он также реализует запросы свойств типа N4428 , текущая версия предложения по отражению в 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" был преобразован в перечисление во время компиляции.

person antron    schedule 11.07.2015
comment
Привет, @mrhthepie, извини, что твое изменение было отклонено. Я только что увидел письмо об этом. Я собираюсь включить это в ответ - спасибо за исправление! - person antron; 24.07.2016
comment
отлично. Будет ли это работать, если мне нужно перечисление битов? Например, мне нужно перечисление BitFlags, каждый из которых 1U сдвинут на некоторую величину? - person user3240688; 03.08.2016
comment
Можете ли вы уточнить, как вы заставили его работать с битовыми флагами? - person user3240688; 12.08.2016
comment
Я не уверен, что вы имеете в виду. Как использовать его для битовых флагов или как внутреннее устройство поддерживает такое использование? Использование ENUM(Foo, uint32_t, A = 1 << 0, B = 1 << 1, C = 1 << 2). Внутренние компоненты поддерживают это, потому что макрос использует обычный enum внутри для объявления констант (см. Текст ответа и код). - person antron; 13.08.2016
comment
похоже, есть утечка памяти в _trimmed_names() в коде, который вы разместили здесь (new char[length + 1], но вы не устанавливаете initialized в значение true). я что-то упускаю? Я не вижу такой проблемы в вашем коде на github. - person user3240688; 03.11.2016
comment
Он установлен в true, но за пределами ветки if (утечка памяти, первоначально обнаруженная @mrhthepie). Надо переместить внутрь ... Редактирование. Спасибо за внимательный взгляд на этот код и код GH. - person antron; 07.11.2016
comment
Я получаю ошибку компиляции, если использую класс ENUM в качестве параметра в конструкторе преобразования для другого класса и пытаюсь перегрузить operator<< для этого класса. Например. ENUM(Foo, char, A = 1, B ) , а затем class Bar { public: Bar() {} Bar (Foo v) {} }; inline std::ostream& operator<<( std::ostream& out, const Bar& o ) { return out; }, а затем в моем main(), я делаю это Foo f = Foo:A; cout << f << endl; Он жалуется на неоднозначную перегрузку operator<<. Почему он это делает? - person user3240688; 12.12.2016
comment
@ user3240688 Я только что тестировал версию 0.11.1 на MSVC 2013, и mingw 4.8.2 передали ваш фрагмент кода (опечатка? Foo :: A). - person zhaorufei; 13.12.2016
comment
Я протестировал версию 0.11.1 и нашел кое-что, что нужно улучшить: * Объявление / определение голого перечисления c ++ может появиться внутри определения класса и тела функции, лучше перечисление не допускает этого (из-за пространства имен?) * Пустое перечисление - это разрешено в C ++: enum X {}; но лучше enum вызовет долгую ошибку компилятора. * Использование инициализации статической переменной по требованию в функции может иметь потенциальное состояние гонки в многопоточной среде. - person zhaorufei; 13.12.2016
comment
to_string может вернуть string_view из C ++ 17, который не требует завершения нуля, и стать constexpr. - person Yakk - Adam Nevraumont; 01.09.2017
comment
Мне непонятно, в чем разница между вами ENUM и вашей библиотекой BETTER_ENUM. Во-первых, кажется, что я не могу сравнить BETTER_ENUM (поэтому создайте его, а затем сравните его с одним из его предопределенных значений, не удастся с какой-то странной ошибкой компиляции). Для двух ENUM реализован только для 8 значений (а BETTER_ENUM намного больше). - person ikku100; 16.11.2017
comment
Можете ли вы обновить этот ответ сейчас, когда был выпущен C ++ 17? В частности, вы ссылаетесь на определенные предложения, которые могли или не могли войти в окончательную версию. - person einpoklum; 11.03.2018
comment
@einpoklum У меня сейчас нет времени хорошо разбираться в этом. Если вы или кто-то другой предлагает отредактировать, я могу проверить это намного быстрее и принять. - person antron; 12.03.2018

Что касается 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 clang ):

#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 г. в Issaquah), но есть уверенность, что он попадет в C ++ 20; из отчета о поездке Херба Саттера:

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

person ecatmur    schedule 03.03.2015
comment
@antron извините, ваше редактирование было отклонено; Я бы одобрил это, если бы увидел вовремя. Я не видел N4428, так что спасибо, что отказались. - person ecatmur; 05.08.2015
comment
Нет проблем, спасибо, что добавили. Интересно, почему это было отклонено. Я вижу, что это не делает его более точным, шаблонным, но для сегодняшнего дня он явно более точен. - person antron; 05.08.2015
comment
Спасибо :-) Я разделил последний пример, чтобы избежать горизонтальной полосы прокрутки. Как жаль, что значение MyEnum::AAA не может быть передано в качестве второго аргумента std::meta::get_enumerators_m: - / - person oHo; 16.01.2017
comment
Тот факт, что такая простая в концептуальном плане задача требует трех уровней вложенных аргументов шаблона, сильно переоценен. Я уверен, что для этого есть конкретные технические причины. Но это не означает, что конечный результат удобен для пользователя. Я люблю C ++, и этот код мне понятен. Но 90% других программистов, с которыми я работаю ежедневно, избегают C ++ из-за такого кода. Я разочарован тем, что не видел более простых и встроенных решений. - person void.pointer; 22.02.2018
comment
@ void.pointer: Не обязательно. В таких языках, как Java, это, по сути, однострочный. Но есть одна загвоздка: вам нужна виртуальная машина на миллионы строк кода. В C ++ у нас этого нет, поэтому мы создаем наши абстракции, используя более простые или более общие возможности языка. Эти 90% программистов должны просто не пытаться делать это сами, а полагаться на библиотеки, написанные другими. - person einpoklum; 05.04.2018
comment
@einpoklum не требует виртуальной машины. Компилятор может построить отображение строк во время компиляции. Строки совпадают с именами счетчиков. Зачем об этом думать? - person void.pointer; 05.04.2018
comment
Похоже, что текущая оценка включения предстоящего Reflection TS в стандарт - C ++ 23: grassutter.com/2018/04/02/ - person Tim Rae; 25.04.2018
comment
@einpoklum Компилятор уже автоматически генерирует конструкторы и деструкторы по умолчанию, почему бы не автоматически генерировать ostream operator<<(ostream, THING) всякий раз, когда он видит что-то вроде std::cout << std::meta(classInstance) или std::meta(enumvalue) - person Troyseph; 08.06.2018

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

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

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

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

  • '+' '+ =' для 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;
}

Вы можете запустить код здесь

person Danilo Ramos    schedule 16.02.2018
comment
Можем ли мы иметь разрывы строк внутри этого определения макроса? - person einpoklum; 11.03.2018
comment
Добавлены разрывы строк внутри этого определения макроса по запросу. - person Danilo Ramos; 28.03.2018
comment
+1. Любите ваше решение. Самая компактная версия и она работает. Даже для своей цели я мог бы минимизировать его, потому что мне не нужны std::string перегрузки. - person Peter VARGA; 24.05.2018
comment
Я добавил перегрузку для *, чтобы получить количество перечислений ... Надеюсь, вы не против :-) - person Peter VARGA; 24.05.2018
comment
Есть ли причина, по которой эта реализация использует std::map (O (log (n)) indexing), а не std::unordered_map (O (1) indexing)? - person River Tam; 03.09.2018
comment
Кроме того, я думаю, что методы должны быть помечены inline, чтобы вы могли объявлять перечисления в файлах заголовков, как обычно, без получения множественного определения ошибок от компоновщика. (не уверен, что это действительно самое чистое / лучшее решение) - person River Tam; 03.09.2018
comment
(извините за спам, но сегодня я не могу редактировать комментарии) есть и другие проблемы, связанные с тем, что это находится в файле заголовка. Карту (E##MapName) необходимо переместить в модуль компиляции, который также имеет доступ к перечислению. Я создал решение, но оно не очень чистое, и мне нужно получить разрешение, чтобы поделиться им. На данный момент я просто комментирую, чтобы сказать, что нет смысла отмечать методы как встроенные без дополнительных функций, необходимых для поддержки использования в файле заголовка. - person River Tam; 03.09.2018
comment
Я не полностью удовлетворен выбором + (это кажется неправильным наряду с отсутствием, скажем, op+(string, int)), но я не знаю, что еще предложить. Может быть, использовать ToString бесплатную функцию? - person Lightness Races in Orbit; 05.10.2018
comment
Это решение не поддерживает использование constexpr выражений в качестве значений для перечислений. - person haelix; 14.01.2019

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

Моя текущая процедура - запустить Vim, скопировать перечислители в пустое тело переключателя, запустить новый макрос, преобразовать первый перечислитель в оператор case, переместить курсор в начало следующей строки, остановить макрос и сгенерируйте оставшиеся операторы 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);
}

И вот как я живу.

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

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

person StackedCrooked    schedule 11.03.2015
comment
Я делаю именно это. Хотя я обычно использую Surround vim и блокирую выделение по пути - person sehe; 12.03.2015
comment
@sehe Интересно. Я должен взглянуть на объемное звучание, потому что в настоящее время мне требуется много нажатий клавиш. - person StackedCrooked; 12.03.2015
comment
Вот он полностью, без макросов (если не засчитывается .): i.imgur.com/gY4ZhBE.gif - person sehe; 12.03.2015
comment
Анимированный гиф милый, но сложно сказать, когда он начинается и заканчивается, и как далеко мы зашли. ... на самом деле, поцарапайте это, это не мило, это отвлекает. Я говорю убить его. - person einpoklum; 11.03.2018
comment
Такой подход к выбору блоков в vim хорош и все такое, но почему бы просто не использовать что-то вроде :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/? - person Ruslan; 09.04.2018

Я не знаю, понравится вам это или нет, я не очень доволен этим решением, но это дружественный подход к 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;
}

Хуже всего в этом подходе то, что его сложно поддерживать, но также сложно поддерживать некоторые другие подобные подходы, не так ли?

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

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

Живой пример

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

Загадочный 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 ...);
}

Лучший трюк здесь - это использование шаблона переменных для карты, которая содержит значения и имена каждой записи перечисления; эта карта будет одинаковой в каждой единице перевода и везде будет иметь одно и то же имя, поэтому она будет довольно простой и понятной, если мы вызовем функцию 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';

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

Живой пример

person PaperBirdMaster    schedule 19.03.2015
comment
Это выглядит довольно аккуратно (можно ли просто не определять неспециализированную переменную?). Возможно, я что-то упускаю, хотя я вообще не понимаю, как он обрабатывает случай выполнения. - person user673679; 19.12.2015
comment
@Paula_plus_plus: Разве вы не должны просто использовать std::array вместо громоздкой карты? Это станет предпочтительным только для перечислений, начинающихся с ... чего, значений 2 ^ 10? Возможно даже больше. - person einpoklum; 11.03.2018
comment
@einpoklum, было бы здорово, если бы мы могли гарантировать во время выполнения, сколько элементов имеет enum. К сожалению, мы не можем. И весь смысл карты состоит в том, чтобы просто связать имена со значениями, для чего std::map подходит. - person PaperBirdMaster; 11.03.2018
comment
@Paula_plus_plus: вы уже вызываете функцию initialize(), количество аргументов которой равно количеству значений перечисления, поэтому вы знаете количество значений во время компиляции. Только конкретное значение, которое вас просят напечатать, известно только во время выполнения. Кроме того, даже если вы не знаете это число, std :: vector будет быстрее, чем std :: map, опять же, почти во всех реалистичных случаях. - person einpoklum; 11.03.2018
comment
@einpoklum, это действительно хороший аргумент, я подумаю об этом, спасибо! Единственное, что меня беспокоит, так это то, что std::array не является контейнером "ключ-значение" и поэтому в нем отсутствуют методы поиска; в любом случае я подумаю. - person PaperBirdMaster; 11.03.2018
comment
@Paula_plus_plus: std::find - ваш друг. - person einpoklum; 11.03.2018
comment
Я нашел этот вопрос несколько месяцев спустя, но причина НЕ использовать массив или вектор будет в том, что сами значения перечисления не начинаются с нуля. Перечисления иногда могут использоваться для типов сообщений из двоичных данных, поэтому, возможно, их значения начинаются выше 2 ^ 10, даже если в перечислении всего несколько десятков значений, потому что они соответствуют реальным значениям во встроенной системе или тому подобное. - person Kevin Anderson; 04.06.2018

Если ваш 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 ++, но его можно использовать для генерации большого количества кода без особых усилий и обслуживания.

person eferion    schedule 31.03.2017
comment
Отдельный файл не нужен. По сути, это x-macro. - person HolyBlackCat; 18.01.2018
comment
@HolyBlackCat, если вы разделите решение в некоторых файлах, вы можете повторно использовать значения перечисления для разных целей - person eferion; 18.01.2018
comment
Я пытаюсь сказать, что вы можете сделать то же самое, если поместите список значений в один макрос вместе с определением перечисления в заголовке. - person HolyBlackCat; 18.01.2018
comment
@HolyBlackCat да, я вас понимаю, но предпочитаю это решение. с другой стороны, это решение можно найти в исходном коде clang, поэтому я думаю, что это хороший способ решить проблему. - person eferion; 18.01.2018
comment
Справедливо. Думаю, не стоило отрицать это, так как это действительно может иметь некоторые применения. (Простите за фиктивную правку, иначе система заблокирует мой голос.) - person HolyBlackCat; 18.01.2018
comment
@HolyBlackCat не волнуйтесь ... Спасибо, что прочитали ответ и высказали свое мнение - person eferion; 18.01.2018
comment
В clang определения находятся в отдельном исходном файле, потому что они автоматически генерируются инструментом tablegen. - person gigabytes; 05.09.2019

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

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

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/a.h:

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

person Mense    schedule 14.03.2016
comment
Ух ты! Очень оригинальная и новаторская идея :-) Надеюсь, у вас хватит смелости обновить свой генератор, чтобы предоставить версии constexpr и noexcept ;-) Я также только что посмотрел на ваш проект GitHub ;-) Ура - person oHo; 17.03.2016
comment
Обновил генератор. Теперь функции всегда будут constexpr и enum: ‹type› теперь поддерживается. Спасибо за звезду :) - person Mense; 17.03.2016
comment
Ссылка не работает ... -.- - person yeoman; 16.06.2016
comment
Ссылка теперь исправлена. - person Mense; 16.06.2016

По запросу OP, здесь урезанная версия уродливого решения для макросов, основанная на Boost Preprosessor и макросы с различными вариантами .

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

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
person PlasmaHH    schedule 03.03.2015
comment
Этот блок кода - безумное путешествие по удивительным просторам метапрограммирования черной магии. Я действительно почувствовал облегчение, когда добрался до main - Дом, милый дом! - person Quentin; 03.03.2015
comment
Просто добавил ссылку на coliru для проверки вывода (есть некоторые предупреждения, щелкните ссылку в своем ответе). Я также разделился на Lib / Usage. Можно ли переместить материал namespace xxx в место заголовка? Вы можете сказать во вступлении, что используете boost/preprocessor.hpp, и поэтому ответ совместим с современным C ++. Пожалуйста, исправьте предупреждения и немного очистите исходный код для лучшего качества. - person oHo; 03.03.2015
comment
@olibre: Это копия, я думаю, из пяти разных заголовков в нашей библиотеке. Enum_cast взят из другой, более общей части, но я подумал добавить и его, чтобы увидеть, для чего нужен do_enum_cast в макросе .. Предупреждения только из моего main<tab> vim, включая аргументы, которые я не использую. Я не думаю, что этот код можно действительно очистить, он просто показывает, что можно и чего нельзя делать;) и если я изменю его здесь, это уже не тот код, который я использую в производстве ... это одна из тех хрупких вещей что, как только это сработает, лучше никогда не трогать, потому что он может рухнуть так, как никто не мог предсказать. - person PlasmaHH; 03.03.2015
comment
Хорошо, Plasma, я вижу, это можно рассматривать как Подтверждение концепции. Но накладных расходов на макрос слишком много, чтобы проголосовать за него. Тем не менее, спасибо, что поделились. Ваше здоровье - person oHo; 04.03.2015
comment
Привет, плазма. Я выполнил глубокую очистку исходного кода + завершил компиляцию и запуск вывода. Пожалуйста, проверьте мою правку. Я надеюсь, что это нормально для тебя. Является ли ответ более ценным? Однако накладные расходы на макрос по-прежнему ужасны! Хорошего дня :-) Ура - person oHo; 04.03.2015
comment
@olibre: я думаю, что это не может быть сделано без ужасных накладных расходов, но если он находится в разных слоях одной библиотеки, с ним можно справиться. Я думаю, что библиотеки для этого - скрыть ужасные вещи. Вы когда-нибудь пробовали читать код ускорения? особенно часть mpl до C ++ 11 ... - person PlasmaHH; 04.03.2015
comment
Да ты прав, согласен. Взгляните на ответ Матье, также основанный на препроцессоре Boost, но с меньшими накладными расходами на макрос. Хорошо, его решение предоставляет меньше услуг, чем ваша библиотека ... Ура - person oHo; 04.03.2015
comment
@olibre: да, получение части x = y было серьезным препятствием. Также я предпочел синтаксис (a, b, c) вместо (a) (b) (c), чтобы все выглядело больше как настоящие списки перечислений. Оба резко увеличили потребность в магии. - person PlasmaHH; 04.03.2015
comment
Ужасная читаемость макромагии этого уровня - вот почему я так предпочитаю генерацию исходного кода. Магия макроса böack кажется настолько умной, что люди не понимают ее и не начинают просить вас исправить свои загадочные ошибки компилятора, которые они получают из-за неправильного хранения. Или до тех пор, пока люди не застрянут при отладке несвязанной ошибки в коде с помощью макроса. - person yeoman; 09.06.2019
comment
@yeoman: у обоих есть свои плюсы и минусы. если вы скармливаете такому макросу просто список перечислений, добавление одного становится таким же простым, как добавление его в список аргументов макроса. В случае создания кода вам, вероятно, придется добавить его в специальное место и запустить специальный инструмент. - person PlasmaHH; 12.06.2019
comment
@PlasmaHH, это абсолютно точно ???? - person yeoman; 13.06.2019
comment
За исключением того, что специальный инструмент можно легко интегрировать в make / cmake & c. построить прежде всего, чтобы он работал даже перед лицом несвязанных ошибок компилятора ???? - person yeoman; 13.06.2019
comment
@yeoman: как я уже сказал, все за и против. Я бы, например, не хотел, чтобы библиотека только для заголовков поставлялась с набором инструментов, которые мне нужно запускать в дополнение к компиляции. - person PlasmaHH; 17.06.2019

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

Генератор кода на 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()
person yeoman    schedule 28.05.2016
comment
Большое спасибо за то, что вы поделились своим генератором на двух языках :-) Но знаете ли вы, как сгенерировать во время компиляции? Например, можем ли мы представить перевод вашего генератора с помощью операторов CMake для обновления кода, сгенерированного C ++, при изменении входных данных? Моя мечта - заставить компилятор C ++ генерировать перечисления при компиляции с использованием метапрограммирования (функции variadic template class и constexpr). - person oHo; 07.06.2016
comment
Ото, на случай, если добавить настраиваемую команду cmake слишком сложно, вы можете автоматизировать свою IDE или вызвать генератор вручную и получить вывод в системе управления версиями. Иногда неплохо было бы сгенерировать код в системе управления версиями в любом случае, если это не слишком много, и люди понимают, что они не должны вносить изменения вручную, потому что иногда интересно посмотреть на историю сгенерированных файлов, когда вы отлаживаете что-то странное и подозреваете, что недавнее изменение в генераторе могло что-то сломать :) - person yeoman; 08.06.2016
comment
Что касается генерации чего-либо во время компиляции, это так просто в LISP, потому что синтаксис очень чистый и простой. Этому способствует тот факт, что он динамически типизирован, что позволяет ему быть кратким и читаемым без особого синтаксиса. Для эквивалента макросов LISP в C ++ потребуется очень сложный способ описания AST того, что вы пытаетесь сгенерировать. А AST для C ++ никогда не бывает красивым :( - person yeoman; 08.06.2016
comment
Прямо в Make, а не в cmake, кстати, это очень просто. Просто сгенерируйте цели .h и .cpp для каждого файла .enum с помощью find, и пусть эти цели зависят от указанных значений enum def, чтобы они автоматически генерировались заново после изменения файлов .enum def. В cmake, вероятно, намного проще, потому что он полон магии для такого рода вещей, но я регулярно использую Make, ant и gradle, но имею лишь ограниченные знания о Maven, cmake и grunt :) - person yeoman; 08.06.2016
comment
Спасибо за ваш ответ :-) Я думаю, что большинство разработчиков C ++ оценят, если ваш генератор сможет обнаруживать перечисления непосредственно в коде C ++, например enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};, или в нескольких строках :-D Как вы думаете, вы можете адаптировать свой генератор, чтобы обнаруживать enum внутри файл C ++? Лучше всего сгенерировать код только при обнаружении тега типа /*<Generate enum to string here>*/. Затем ваш генератор записывает на месте соответствующий сгенерированный код C ++ (заменяя предыдущий сгенерированный код). ^ _ ^ Какой классный генератор, не правда ли? Ваше здоровье :-) - person oHo; 09.06.2016
comment
Замена кода на место, как правило, плохая идея. Что, если вы решите добавить двадцать новых полей перечисления? тогда этот ввод теряется, потому что он был перезаписан. плюс, когда вы пытаетесь сделать его SMART, оставляя входную таверну на месте и добавляя сгенерированный код прямо под ней, и заменяя свой сгенерированный код вновь сгенерированным кодом в случае изменения входных данных, вы можете поспорить, что рано или поздно что-то пойдет не так, и ваш генератор удалит какой-то код, которого не должно было -.- - person yeoman; 10.06.2016
comment
Если для вас неприемлемо наличие в вашем проекте текстового файла, не являющегося исходным кодом C ++, и вам платят за ваше время, а не за фактическую работу, которую вы выполняете, я предлагаю вручную написать эквивалентные ручные перечисления. Но не забудьте написать пару модульных тестов для каждого, чтобы случайно не создать типичные ошибки копирования и вставки :) - person yeoman; 10.06.2016
comment
Спасибо за отзыв :-) +1 Вы правы, изменение содержимого исходного файла C ++ - не лучшая идея (например, файл может быть доступен только для чтения). Следовательно, сгенерированный код должен быть где-то рядом с другими сгенерированными файлами (например, файлами * .o). Используя вашу идею, цепочка инструментов сборки (Makefile, CMake или ...) может вызвать внешний модуль (на Java или Python ...) для обнаружения перечислений непосредственно в коде C ++ и генерации соответствующих функций enum_to_string. Ваш код может развиваться, чтобы использовать clang-parser для понимания абстрактного синтаксического дерева (AST) источника C ++ ... Ура - person oHo; 10.06.2016
comment
Это добавляет сложности и требует высокой цены. Код C ++ легко отлаживать. Объектный файл без реального источника не является -.- - person yeoman; 10.06.2016
comment
Кстати. В чем проблема с исходными файлами на простом языке ввода генератора, которые автоматически встраиваются в сгенерированные исходные файлы C ++, которые затем автоматически компилируются с помощью компилятора C ++? Какая для вас болевая точка? :) :) - person yeoman; 10.06.2016
comment
Приносим извинения за путаницу с созданными C ++ и объектными файлами. Я имею в виду, что сгенерированные файлы C ++ должны быть записаны в каталог для чтения / записи. И объектные файлы также должны быть записаны в каталоги для чтения / записи. Сгенерированные файлы C ++ не должны записываться вместе с входными файлами C ++ (например, каталог может быть доступен только для чтения). Я пришел к выводу, что сгенерированные файлы C ++ должны быть записаны в том же дереве каталогов, что и объектные файлы. Как и объектные файлы, эти сгенерированные файлы C ++ являются временными. На мой взгляд, все эти временные элементы следует удалить при полной очистке. - person oHo; 11.06.2016
comment
Представьте себе два инструмента для создания C ++. Оба инструмента просты в использовании и надежны. Разница: (1) С первым инструментом разработчик записывает псевдокод enum в файл конфигурации, и инструмент генерирует как enum, так и enum_to_string функцию. (2) С помощью второго инструмента разработчик продолжает писать enum на C ++ (в правильном пространстве имен / классе), и инструмент генерирует только соответствующую enum_to_string функцию. Я думаю, что большинство разработчиков предпочтут второй инструмент. Я не говорю, что с первым инструментом есть проблемы. Я просто считаю, что второй удобнее. Ты согласен? - person oHo; 11.06.2016
comment
Да, дополнительный каталог для сгенерированных исходных файлов является обязательным :) Я в основном называю его сгенерированным и генерирую предупреждающий комментарий поверх каждого сгенерированного исходного файла, в котором говорится, что он будет перезаписан :) - person yeoman; 11.06.2016
comment
Если перечисление является классом перечисления C ++, то да, вы можете записать его в файл заголовка. Но вам все еще нужна информация об имени сгенерированного заголовка и исходного файла, а также заголовочного файла для чтения перечисления и т. Д. В конфигурации, поэтому вы получаете два источника, плюс полученное перечисление затем распространяется по нескольким файлам, и дополнительный файл заголовка должен быть включен вручную, потому что генератор не будет касаться ни одного из ваших файлов ручного кода. - person yeoman; 11.06.2016
comment
Если, otoh, перечисление на самом деле является типизированным классом перечисления со всеми видами встроенных потенциальных дополнительных функций, вы хотите, чтобы все они были сгенерированы в любом случае, потому что сам заголовок тогда содержит много шаблонов. Я обычно использую этот подход, а также создаю множество других вещей, таких как структуры, классы соединений и т. Д. для нескольких платформ (C ++, Java, Python, C #, Objective-C, Swift), поэтому я в любом случае работаю с дополнительными файлами IDL, а перечисления живут там со всем остальным, и сейчас это кажется мне вполне естественным :) - person yeoman; 11.06.2016
comment
Для перечислений C ++ и только перечислений с дополнительными файлами заголовков для функций поддержки перечислений я думаю, что работать с llvm для доступа к AST - плохая идея, потому что llvm не только является действительно большой базой кода, но и MESS. Я редко видел менее читаемый код, чем в llvm. - person yeoman; 11.06.2016
comment
Кроме того, есть мир за пределами llvm, и классы перечисления достаточно легко анализировать с помощью простого регулярного выражения, особенно с вспомогательным комментарием, содержащим всю информацию для генератора, поэтому нет дополнительного файла конфигурации (в этом случае я думаю, что важно, потому что один источник всегда отличная идея), а Python делает все независимо от llvm, так что вы можете делать это даже в Windows или в классической настройке gcc (а мне очень не нравится использовать сразу несколько компиляторов) :) - person yeoman; 11.06.2016
comment
Итак, в настройке с перечислениями C ++ ваша идея побеждает, но с python и нет дополнительного файла конфигурации :) - person yeoman; 11.06.2016
comment
Спасибо, Йомен, за твои объяснения. Вы правы, более того, написать генератор, читающий простой ввод текста, намного проще, чем из сложного ввода C ++ (перечисление C ++ может быть запутано с помощью #define MACRO). - person oHo; 13.06.2016
comment
Но использовать Clang AST не должно быть так сложно. Например, Loïc представил на встрече C ++ в Париже (C ++ FRUG) инструмент для визуализации AST из Код C ++, который вы вставляете в его левые окна: Clang-ast-viewer. Можно представить, что код генерируется только в том случае, если объявлена ​​функция std::string EnumX_to_string(EnumX). Сгенерированное имя файла *.cpp выводится из имени файла, в котором объявлена ​​эта функция. Не нужно его включать. Мне понравилось наше конструктивное обсуждение ;-) Ура - person oHo; 13.06.2016
comment
:) :) Использование AST от clang, конечно же, далеко не сложно или невозможно. Но по сравнению с тем, чтобы ничего не делать вообще, потому что у меня были запущены и запущены мои входные IDL и инструменты синтаксического анализа, это действительно было бы монументальным усилием, требующим нескольких дней работы :) - person yeoman; 15.06.2016
comment
Кстати. Несмотря на то, что кодовая база llvm представляет собой беспорядок, это все еще интересно, и я действительно рекомендую вам заглянуть внутрь и, возможно, попытаться достать AST перечисления и попытаться создать что-то из него. Это будет очень проницательно о том, как на самом деле все работает в LLVM в деталях, как ДЕЙСТВИТЕЛЬНО производится колбаса, за блестящим фасадом с лязгом и быстрым, а также маркетингом Apple и шумихой вокруг него, что я нашел неожиданным образом увлекательным. славный и устрашающий: D - person yeoman; 15.06.2016
comment
Просто из любопытства - Лоик - бретонское имя? :) - person yeoman; 30.06.2016
comment
Насколько я понимаю, все знакомые мне Loïc происходят из Бретани (или их родителей). Мои коллеги в Париже также думают, что Лоик - это бретонское имя. Но после прочтения статьи Loïc в Википедии я вижу, что Loïc может происходить от старой провансальской формы Людовика или от греческого имени Лукас ... В следующий раз я встречусь с Лоик Джоли Я спрошу его, не из Бретани ... - person oHo; 01.07.2016

Если вы можете написать отдельную пару .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 преобразует каждый член перечисления в значения списка инициализаторов, которые впоследствии передаются в конструктор элемента, чтобы заполнить информацию перечисления.

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

Преимущества

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

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

Недостатки

  • Требуется отдельная .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
}
person Jason Lim    schedule 24.05.2018

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

#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.

person Ignace    schedule 19.10.2017
comment
Это не работает с g ++ 6.3.0 и C ++ 14. - person einpoklum; 11.03.2018
comment
Интересно, потому что перечисление можно объявить обычным образом, не заключая его в макрос. Хотя мне не нравятся зависимости компилятора и магические константы. - person zett42; 26.08.2018

Я взял идею от @antron и реализовал ее по-другому: сгенерировал настоящий enum class.

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

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

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

Самое главное, для этого требуется всего 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); \
    } 
person Yuri Finkelstein    schedule 18.01.2018

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

Я хочу включить эквивалент

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

Я не фанат макросов. Однако, если С ++ не поддерживает преобразование перечислений в строки, нужно использовать какую-то генерацию кода и / или макросы (и я сомневаюсь, что это произойдет слишком скоро). Я использую 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 через включение. Использование похоже на это

#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-макросы, пока я не написал свой собственный;).

person 463035818_is_not_a_number    schedule 12.01.2019

Следующее решение основано на 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 можно было использовать внутри класса.

person FKaria    schedule 04.03.2016
comment
Спасибо за интересный ответ. Пожалуйста, переработайте свое предложение, чтобы использовать макрос в классе. См. coliru.stacked-crooked.com/a/00d362eba836d04b Более того, попробуйте использовать constexprand noexept ключевые слова, когда это возможно. Ваше здоровье :-) - person oHo; 04.03.2016
comment
В вопросе не указан этот реквизит. - person FKaria; 04.03.2016
comment
Вопрос обновлен (см. Пример). Два других требования: (1) тип поддержки enum и (2) значения могут отличаться от последовательности 0, 1, 2 ... - person oHo; 04.03.2016
comment
Я переработал свое решение, чтобы его можно было использовать внутри класса. Я не понял, как сделать значения отличными от 0,1,2, .. хотя. - person FKaria; 20.05.2016
comment
Привет FKaria. Большое спасибо за переделку. Я внес некоторые изменения для поддержки нескольких перечислений в одном классе, а также для поддержки формата enum class X : Type. Просмотрите мой вклад: coliru.stacked-crooked.com/a/b02db9190d3491a3 Что делать ты думаешь о моих изменениях? Есть ли у вас идея поддерживать значения, установленные в enum? Пример enum E{A=3, B=6, C=A-B}; Ура - person oHo; 08.06.2016
comment
Мое решение должно поддерживать несколько перечислений в одном классе. Хотя вам нужно использовать синтаксис X :: Enum, который может быть немного уродливым. Я не знал о возможности указать тип для перечислений, это приятное дополнение. Что касается присваивания enum, я еще не смотрел его, но я предполагаю, что для этого потребуется некоторый макрос стиля ENUM(X, (A,3)(B,6)(C,A-B)), если это вообще возможно. - person FKaria; 13.06.2016

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

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 он не компилируется. Вам следует быть осторожным, если вы перегружаете две функции, принимающие перечисление и целое число, или удаляете неявное преобразование ...

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

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;
};
person user    schedule 23.10.2016
comment
Это очень интересно :-) Однако ваша текущая версия подразумевает, что вам нужно вручную писать case Enum::RED: return "red";. Речь идет об автоматизации этого материала компилятором (во время компиляции). Идея вопроса состоит в том, чтобы изменить или добавить только значения перечисления без необходимости обновлять материал toString(). Ты видишь? Спасибо - person oHo; 26.10.2016

Этот gist предоставляет простое сопоставление на основе вариативных шаблонов 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
person hutorny    schedule 21.01.2017
comment
Очень интересный способ метапрограммирования. Я попытался немного упростить ответ, чтобы он был автономным (без зависимости от ссылки Gist). Чтобы быть кратким и понятным, я наконец-то сильно отредактировал ваш ответ. Вы все еще согласны с моими изменениями? Ваше здоровье ;-) - person oHo; 23.01.2017

Очень простое решение с одним большим ограничением: вы не можете присвоить пользовательские значения 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);
person malem    schedule 19.07.2017
comment
Спасибо, Малем, за вашу новаторскую идею. Я отредактировал ваш ответ, чтобы улучшить читаемость. Надеюсь, вам понравятся мои изменения. Продолжайте улучшать свой ответ: (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? ... Ваше здоровье :-) - person oHo; 20.07.2017
comment
Мне очень нравится такой подход. Действительно коротко и легко для понимания. - person Anton Holmberg; 24.03.2018

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

Как упоминалось выше, 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 тип, поэтому его нельзя продвигать до int
  • Не может использоваться в корпусах переключателей

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

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

#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 конфликтует с -pedantic на GCC и лязгает.

Обходной путь

Либо начните с #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;
}

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

https://github.com/bit2shift/r3dVoxel/blob/c76af954c55480a33c0213c1c25c121fa33043fd/inc/r3dVoxel/util/Enum.hpp

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

#line белье - cppreference.com

person bit2shift    schedule 05.07.2015

Вы можете использовать библиотеку отражения, например 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"
person Nick    schedule 05.10.2018

(Аналог https://stackoverflow.com/a/54967187/2338477, с небольшими изменениями).

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

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

#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

person TarmoPikaro    schedule 03.03.2019

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

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

Используйте макрос 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););
}

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


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

person Madwyn    schedule 10.11.2015
comment
Привет Мэдвин. Спасибо за идею. Но как это работает? Какие накладные расходы? (нулевые накладные расходы или это создает дополнительные данные?). Ваше предложение кажется прекрасным, но, к сожалению, для каждого enum значения необходимо использовать / обновлять / поддерживать одно выражение DEF_MSG: - / И это то, что в идеале мы хотели бы прекратить делать ... Ура - person oHo; 13.11.2015
comment
Спасибо за ответ, @olibre. Пожалуйста, проверьте обновленный ответ. Я не вижу здесь накладных расходов, за исключением того, что для доступа к строкам требуется вызов функции. DEF_MSG делает enum тесно связанным с сообщением, хотя и имеет некоторые ограничения. - person Madwyn; 13.11.2015
comment
Спасибо за добавленное объяснение в вашем ответе :-) Ваша библиотека в порядке, но не может использоваться для нескольких перечислений: - / А как насчет поддержки _ 1_ (C ++ 11)? Вы можете использовать constexpr для ограничения _g_messages во время выполнения. Поддержка нескольких enum типов (исключение _g_messages) с использованием метапрограммирования (тип, передающий {enum-type, enum-value}) или, возможно, переменные шаблона (C ++ 14). Я думаю, что ваша библиотека (пока?) Не соответствует требованиям C ++ 11/14/17. Что вы думаете? Ваше здоровье ;-) - person oHo; 23.11.2015
comment
Спасибо за ответ. Я узнал что-то новое сегодня! Переменные класса enum и шаблона выглядят хорошо. Я думаю, что мой ответ был немного не по теме, потому что он был со вкусом C. - person Madwyn; 23.11.2015

#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_MAKE.

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

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

person desperado_98    schedule 19.11.2015
comment
Хотя этот код может ответить на вопрос, было бы лучше объяснить, как он решает проблему, не представляя других, и зачем его использовать. Ответы, содержащие только код, в конечном итоге бесполезны. - person JAL; 19.11.2015
comment
Привет, ребята, извините, я не очень хорошо говорю по-английски. - person desperado_98; 19.11.2015
comment
макрос ENUM_MAKE автоматически генерирует «класс перечисления» и вспомогательный класс с «функцией отражения перечисления». / Чтобы уменьшить количество ошибок, сразу Все определяется только одним ENUM_MAKE. Преимущество этого кода в том, что автоматически создается рефлексия и пристальный взгляд на код макроса, простой для понимания код. 'enum to string', 'string to enum' производительность оба алгоритма O (1). Недостатки в том, что при первом использовании инициализируется вспомогательный класс для строкового вектора enum relection и map. но если вы хотите, вы также будете предварительно инициализированы. - person desperado_98; 19.11.2015
comment
Привет, desperado_98. Спасибо за ваш вклад. Пожалуйста, отредактируйте свой ответ и вставьте в него свой комментарий. Компилятор может вычислить ваш пример во время компиляции, если вы воспользуетесь некоторыми приемами метапрограммирования и constexpr. Я имею в виду, что функции toName() и toType() можно оценивать во время компиляции, а не во время выполнения (время выполнения). Примените в своем ответе стиль C ++ 11/14/17. Ваше здоровье ;-) - person oHo; 23.11.2015
comment
Более того: совместим ли ваш макрос с enum class MyEnum : short { A, B, C };? - person oHo; 23.11.2015
comment
olibre, я не очень хорошо разбираюсь в макросах, но это не похоже на затворничество. поэтому макрос VA_ARGS должен анализироваться во время выполнения. я не мог решить во время компиляции. - person desperado_98; 04.12.2015
comment
enum class MyEnum: short {A, B, C}; ‹- вы можете протестировать ..., я еще не тестировал, но вроде нет проблем - person desperado_98; 04.12.2015
comment
Макросы не только ухудшают удобочитаемость, когда они достигают определенного уровня сложности, но и создаваемые сообщения об ошибках особенно ужасны. Более того, очень легко нарушить замысел сложного макроса, если это не сделано по-настоящему тщательно. Я лично стараюсь, чтобы макросы не превышали трех-пяти строк, и использую их только там, где есть реальная выгода, без каких-либо разумных альтернатив ... - person yeoman; 27.05.2016

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

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

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

недостатки:

  • вам нужно реплицировать все значения перечислений как текст
  • доступ в хеш-карте должен учитывать строковый регистр
  • обслуживание, если добавление значений болезненно - необходимо добавить как 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];
person Mia Shani    schedule 30.01.2016

Что ж, еще один вариант. Типичный вариант использования - когда вам нужны константы для 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
person cibercitizen1    schedule 14.04.2017
comment
Чтобы уменьшить использование памяти, вы можете заменить элемент const std::string text только на theStrings[v]. Однако вопрос касается возможностей C ++ 11 / C ++ 14 / C ++ 17 / C ++ 20, чтобы избежать написания такого класса вручную: - / - person oHo; 19.04.2017

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

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

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

Полученный результат требует преобразования типа от 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;
}
person tensor5375    schedule 07.09.2018

Мои 3 цента, хотя это не полностью соответствует тому, что хочет опера. Вот соответствующая ссылка.

namespace enums
{

template <typename T, T I, char ...Chars>
struct enums : std::integral_constant<T, I>
{
  static constexpr char const chars[sizeof...(Chars)]{Chars...};
};

template <typename T, T X, typename S, std::size_t ...I>
constexpr auto make(std::index_sequence<I...>) noexcept
{
  return enums<T, X, S().chars[I]...>();
}

#define ENUM(s, n) []() noexcept{\
  struct S { char const (&chars)[sizeof(s)]{s}; };\
  return enums::make<decltype(n), n, S>(\
    std::make_index_sequence<sizeof(s)>());}()

#define ENUM_T(s, n)\
  static constexpr auto s ## _tmp{ENUM(#s, n)};\
  using s ## _enum_t = decltype(s ## _tmp)

template <typename T, typename ...A, std::size_t N>
inline auto map(char const (&s)[N]) noexcept
{
  constexpr auto invalid(~T{});

  auto r{invalid};

  return
    (
      (
        invalid == r ?
          r = std::strncmp(A::chars, s, N) ? invalid : A{} :
          r
      ),
      ...
    );
}

}

int main()
{
  ENUM_T(echo, 0);
  ENUM_T(cat, 1);
  ENUM_T(ls, 2);

  std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;

  std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;

  return 0;
}

Таким образом, вы создаете тип, который можно преобразовать в целое число и / или строку.

person user1095108    schedule 30.08.2020

Мое решение с использованием препроцессора define.

Вы можете проверить этот код на https://repl.it/@JomaCorpFX/nameof#main.cpp

#include <iostream>
#include <stdexcept>
#include <regex>

typedef std::string String;
using namespace std::literals::string_literals;

class Strings
{
public:
    static String TrimStart(const std::string& data)
    {
        String s = data;
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
            return !std::isspace(ch);
        }));
        return s;
    }

    static String TrimEnd(const std::string& data)
    {
        String s = data;
        s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
            return !std::isspace(ch);
        }).base(),
            s.end());
        return s;
    }

    static String Trim(const std::string& data)
    {
        return TrimEnd(TrimStart(data));
    }

    static String Replace(const String& data, const String& toFind, const String& toReplace)
    {
        String result = data;
        size_t pos = 0;
        while ((pos = result.find(toFind, pos)) != String::npos)
        {
            result.replace(pos, toFind.length(), toReplace);
            pos += toReplace.length();
            pos = result.find(toFind, pos);
        }
        return result;
    }

};

static String Nameof(const String& name)
{
    std::smatch groups;
    String str = Strings::Trim(name);
    if (std::regex_match(str, groups, std::regex(u8R"(^&?([_a-zA-Z]\w*(->|\.|::))*([_a-zA-Z]\w*)$)")))
    {
        if (groups.size() == 4)
        {
            return groups[3];
        }
    }
    throw std::invalid_argument(Strings::Replace(u8R"(nameof(#). Invalid identifier "#".)", u8"#", name));
}

#define nameof(name) Nameof(u8## #name ## s)
#define cnameof(name) Nameof(u8## #name ## s).c_str()

enum TokenType {
    COMMA,
    PERIOD,
    Q_MARK
};

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

int main() {
    String greetings = u8"Hello"s;
    std::cout << nameof(COMMA) << std::endl;
    std::cout << nameof(TokenType::PERIOD) << std::endl;
    std::cout << nameof(TokenType::Q_MARK) << std::endl;
    std::cout << nameof(int) << std::endl;
    std::cout << nameof(std::string) << std::endl;
    std::cout << nameof(Strings) << std::endl;
    std::cout << nameof(String) << std::endl;
    std::cout << nameof(greetings) << std::endl;
    std::cout << nameof(&greetings) << std::endl;
    std::cout << nameof(greetings.c_str) << std::endl;
    std::cout << nameof(std::string::npos) << std::endl;
    std::cout << nameof(MyClass::MyEnum::AAA) << std::endl;
    std::cout << nameof(MyClass::MyEnum::BBB) << std::endl;
    std::cout << nameof(MyClass::MyEnum::CCC) << std::endl;


    std::cin.get();
    return 0;
}

Вывод

COMMA
PERIOD
Q_MARK
int
string
Strings
String
greetings
greetings
c_str
npos
AAA
BBB
CCC

Лязг

clang

Visual C ++

введите описание изображения здесь

person Joma    schedule 30.01.2021

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

enum
{
  AAA = "AAA"_h8,
  BB = "BB"_h8,
};
   
std::cout << h8::to_string(AAA) << std::endl;
std::cout << h8::to_string(BB) << std::endl;

Это упаковывает строку в целое число, что является обратимым. Посмотрите пример здесь.

person user1095108    schedule 04.05.2021

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

#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';
}
person dau_sama    schedule 03.03.2015
comment
1) создает еще большее дублирование 2) заставляет использовать потоки - person Karoly Horvath; 03.03.2015
comment
-1 Извините, @dau_sama, но цель всех этих повторяющихся вопросов enum to string - избежать обслуживания сопоставления enum / string. Если вы считаете, что ваш ответ не соответствует цели, удалите его. Удачи тебе в следующем ответе;) Ура - person oHo; 03.03.2015