перерахувати на рядок у сучасних C ++ 11 / C ++ 14 / C ++ 17 та майбутніх C ++ 20


354

На відміну від усіх інших подібних питань, це питання стосується використання нових функцій C ++.

Прочитавши багато відповідей, я ще не знайшов жодної:

  • Елегантний спосіб використання C ++ 11 , C ++ 14 або C ++ 17 нових функцій
  • Або щось готове до використання в Boost
  • Ще щось заплановано на C ++ 20

Приклад

Приклад часто краще, ніж довге пояснення.
Ви можете скласти і запустити цей фрагмент на Coliru .
( Ще один попередній приклад також доступний)

#include <map>
#include <iostream>

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

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

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

Обмеження

  • Будь ласка, не безцінне дублювання інших відповідей або основного посилання .
  • Будь-ласка, уникайте відповіді на основі макросів, або намагайтеся зменшити #defineнакладні витрати як мінімум.
  • Будь ласка, немає інструкції enum-> stringкартографування.

Добре мати

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

Однією з можливих ідей може бути використання можливостей компілятора C ++ для генерації коду C ++ під час компіляції, використовуючи прийоми метапрограмування на основі variadic template classта constexprфункцій ...


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

10
N4113 :std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
екатмур

1
Я особисто вирішив цю проблему, застосувавши невелику бібліотеку утиліт препроцесора, яка дозволяє мені перебирати різноманітні макроаргументи та виконувати функцію над усіма ними. Я передаю значення enum у вигляді макроаргументів і автоматично генерую enum та рядковий масив через препроцесор. Ви, ймовірно, можете це зробити і за допомогою Boost Preprocessor.
Вітторіо Ромео

2
чи все має бути вирішено за допомогою C ++? Настільки легко автоматично генерувати код для представлення рядків, лише пару рядків коду.
Каролі Горват

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

Відповіді:


42

Бібліотека 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.

Значення Enum має бути в діапазоні [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, додайте спеціалізацію enum_range для необхідного типу enum.

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

Чому обмеження діапазону? Це обмеження якоїсь глибини рекурсії чи через якийсь лінійний пошук у компіляції?
Еміль Корм'є

Це дивно. Дякую! Це, мабуть, навіть ефективно, якщо компілятор досить розумний, щоб оцінити constexpr std :: array лише один раз. Дуже-дуже хороший.
iestyn

87

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

Існує спосіб зробити перерахунок на рядок у поточному C ++, який виглядає приблизно так:

ENUM(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"під час компіляції було перетворено на enum.


Heya @mrhthepie, вибачте, що вашу редакцію відхилено. Я щойно побачив електронний лист про це. Я збираюся включити його у відповідь - дякую за помилку!
антрон

це чудово. Це також спрацює, якщо я хочу перелічити біти? Наче я хочу перерахувати BitFlags, кожен з яких 1Uпереміщується на якусь суму?
користувач3240688

1
_trimmed_names()у коді, який ви розмістили тут, схоже, просочилася пам'ять ( new char[length + 1]але ви не встановили initializedзначення true). я щось пропускаю? Я не бачу тієї самої проблеми у вашому коді github.
користувач3240688

1
Він встановлений у true, але поза межами ifгілки (витоку пам’яті спочатку спіймав @mrhthepie). Слід перемістити його всередину ... Редагування Дякуємо за уважний погляд і на це, і на код GH.
антрон

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

74

Для C ++ 17 C ++ 20 вас зацікавить робота дослідницької групи рефлексії (SG7). Існує паралельна серія статей, що висвітлюють формулювання ( P0194 ) та обґрунтування, дизайн та еволюцію ( P0385 ). (Посилання стосуються останнього документа в кожній серії.)

Станом на P0194r2 (2016-10-15) синтаксис використовує запропоноване reflexprключове слово:

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

Наприклад (адаптовано з рефлекторного відгалуження хлопця Матуса Чокліка ):

#include <reflexpr>
#include <iostream>

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

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

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

Статична рефлексія не змогла перетворити його на C ++ 17 (скоріше, у проект, ймовірно, остаточний проект, представлений на зустрічі стандартів у листопаді 2016 року в Іссакві), але є впевненість, що він перетворить його на C ++ 20; із звіту про поїздку Герба Саттера :

Зокрема, дослідницька група " Рефлексія " розглянула останню пропозицію об'єднаної статичної рефлексії та виявила, що готова увійти до основних груп еволюції на нашій наступній зустрічі, щоб розпочати розгляд уніфікованої пропозиції статичного відображення для ТС або для наступного стандарту.


2
@antron вибачте, що вашу редакцію відхилено; Я б схвалив його, якби вчасно його побачив. Я не бачив N4428, тому дякую за те, що вони підняли голови.
екатмур

3
Без проблем, дякую за включення. Мені якось цікаво, чому його відхилили. Я бачу причину котла "не робить це більш точним", але це явно точніше на сьогодні.
антрон

1
Дякую :-) Я розділив останній приклад, щоб уникнути горизонтальної смуги прокрутки. Шкода, що цінність MyEnum::AAAне може бути передана як другий аргумент std::meta::get_enumerators_m: - /
olibre

1
Той факт, що для такої концептуально простої задачі потрібні три рівні вкладених аргументів шаблону, дуже переосмислений. Я впевнений, що для цього є конкретні, технічні причини. Але це не означає, що кінцевий результат є зручним для користувачів. Я люблю C ++ і код має сенс для мене. Але 90% інших програмістів, з якими я працюю щодня, уникають C ++ через такий код. Я розчарований, що не бачив більш простих вбудованих рішень.
void.pointer

2
Здається, що поточна оцінка для включення майбутніх TS-рефлексії до стандарту становить C ++ 23 : biljeutter.com/2018/04/02/…
Тім Рей,

25

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

Декларація класу enum як:

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

Наступний код автоматично створить клас enum та перевантажить:

  • '+' '+ =' для std :: string
  • '<<' для потоків
  • '~' просто для перетворення в рядок (Будь-який унарний оператор буде робити, але мені особисто це не подобається для наочності)
  • '*', щоб отримати кількість перерахунків

Не потрібне підвищення, усі необхідні функції надаються.

Код:

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

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

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

    std::stringstream stringStream(str);

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

    return vecString;
}

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

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

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

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

    return retMap;
}

Приклад:

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

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

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

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

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

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

You can run the code here


1
Чи можна мати розриви рядків всередині цього визначення макросу?
einpoklum

1
Я додав перевантаження, *щоб отримати кількість перерахунків ... Сподіваюся, ви не заперечуєте :-)
Пітер ВАРГА

1
Чи є якась причина, що ця реалізація використовує std::map(O (log (n)) індексацію), а не std::unordered_map(O (1) індексацію)?
Річка Там

1
Крім того, я думаю, що методи повинні бути позначені, inlineщоб ви могли оголосити перерахунки у файлах заголовків як звичайні, не отримуючи помилок "багаторазового визначення". (не впевнений, чи це насправді найчистіше / найкраще рішення)
River Tam

1
(Вибачте, що спам, але я не можу редагувати коментарі сьогодні) Є й інші проблеми, які знаходяться у файлі заголовка. Мапу ( E##MapName) потрібно перемістити до блоку компіляції, який також має доступ до перерахунку. Я створив рішення, але воно не дуже чисте, і мені доведеться отримати дозвіл, щоб поділитися ним. На даний момент я просто коментую, щоб сказати, що немає сенсу маркувати методи вбудованому без додаткових функцій, необхідних для підтримки використання у файлі заголовка.
Річка Там

19

Ще в 2011 році я провів вихідні, допрацьовуючи рішення на основі макросу, і ніколи не використовував його.

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

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

І ось так я проходжусь.

Рідна підтримка для стримування перерахунків була б набагато кращою. Мені дуже цікаво побачити результати робочої групи з роздумів у С ++ 17.

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


1
Я роблю саме це. Хоча я зазвичай використовую Surround VIM і блок вибору шляхом
sehe

@sehe Цікаво. Мені слід придивитись до "оточення", тому що мені зараз потрібен шлях до багатьох натискань клавіш.
StackedCrooked

Тут в повній Горах, що не макроси (якщо .підрахунки): i.imgur.com/gY4ZhBE.gif
sehe

1
Анімований gif милий, але важко сказати, коли він починається і закінчується, і як далеко ми знаходимося. ... насправді, почухайте це, це не мило, це відволікає. Я кажу, вбий.
einpoklum

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

14

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

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

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

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

    return 0;
}

Найгірше в цьому підході є те, що це біль, яку потрібно підтримувати, але це також біль підтримувати деякі інші подібні підходи, чи не так?

Хороші моменти щодо цього підходу:

  • Використання змінних шаблонів (функція C ++ 14)
  • Завдяки спеціалізації шаблону ми можемо «виявити», коли використовується недійсне значення (але я не впевнений, чи це взагалі може бути корисним).
  • Це виглядає акуратно.
  • Пошук імен здійснюється під час компіляції.

Live example

Редагувати

Misterious user673679 ти прав; підхід зі змінним шаблоном C ++ 14 не справляється із випадком виконання, я був винен, щоб забути це :(

Але ми все ще можемо використовувати деякі сучасні функції C ++ та змінний шаблон плюс хитрість варіативного шаблону, щоб досягти перекладу виконання з значення enum у рядок ... це настільки набридливо, як інші, але все-таки варто згадати.

Давайте почнемо використовувати псевдонім шаблону, щоб скоротити доступ до карти "enum-string":

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

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

Потім, хитрість варіативного шаблону:

template <typename ENUM>
void initialize() {}

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

" Найкращий трюк " тут - використання шаблону змінної для карти, який містить значення та назви кожного запису enum; ця карта буде однаковою для кожної одиниці перекладу і матиме однакове найменування скрізь, тому досить просто і акуратно, якщо ми називаємо initializeфункцію так:

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

Ми призначаємо імена кожному MyEnumзапису, які можна використовувати під час виконання:

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

Але можна вдосконалити за допомогою SFINAE та <<оператора перевантаження :

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

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

З правильним operator <<тепер ми можемо використовувати enum таким чином:

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

Це також неприємно підтримувати, і його можна вдосконалити, але сподіваюся, що ви отримаєте ідею.

Live example


Це виглядає досить акуратно (чи можна просто не визначити неспеціалізовану змінну?). Можливо, мені чогось не вистачає, хоча так як я не бачу, як він взагалі справляється із випадком виконання.
user673679

@Paula_plus_plus: Чи не варто просто використовувати std::arrayзамість громіздкої карти? Переважно стане лише для перерахунків, що починаються з ... що, 2 ^ 10 значення? Можливо, навіть більше.
einpoklum

@einpoklum, що було б дивовижно, якщо ми зможемо забезпечити під час виконання кількість елементів enum. На жаль, ми не можемо. І вся суть карти полягає лише в тому, щоб асоціювати імена зі значеннями, для чого std::mapдобре.
PaperBirdMaster

@Paula_plus_plus: Ви вже викликаєте initialize()функцію, число аргументів - кількість значень enum, тож ви знаєте кількість значень під час компіляції. Друкувати лише те саме значення, яке вам потрібно буде надрукувати, і яке відомо лише під час виконання. Крім того, навіть якщо ви не знали цього числа, std :: вектор був би швидше, ніж std :: map, майже в усіх реалістичних випадках.
einpoklum

@einpoklum це дуже хороший момент, я подумаю про це, дякую! Єдине, що викликає занепокоєння, - це те, що std::arrayце не контейнер з ключовими значеннями, а отже, не вистачає методів пошуку; все одно я задумаюсь.
PaperBirdMaster

7

Якщо ваш enumзовнішній вигляд

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

Ви можете перемістити вміст enumфайлу в новий файл:

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

І тоді значення можуть бути оточені макросом:

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

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

// clean up
#undef ITEM

Наступним кроком може бути знову включення елементів enum:

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

І, нарешті, ви можете згенерувати корисні функції щодо цього enum:

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

  return "";
}

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

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

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


3
Немає необхідності в окремому файлі. Це по суті x-макрос .
HolyBlackCat

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

Я намагаюся сказати вам, що ви можете зробити те саме, якщо ви помістите список значень в один макрос поряд із визначенням enum у заголовку.
HolyBlackCat

@HolyBlackCat так, я вас розумію, але я вважаю за краще це рішення. з іншого боку, це рішення можна знайти у вихідному коді
кланг,

Справедливо. Думаю, не слід було б це робити, оскільки це дійсно може мати певну користь. (Вибачте, манекен редагувати, система блокує моє голосування інакше.)
HolyBlackCat

6

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

Використання:

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

Функція здійснює пошук файлів включення у файловій системі (використовує каталоги включення, надані командою include_directories), читає їх і виконує деякий регулярний вираз для створення класу та функції (ів).

ПРИМІТКА: constexpr має на увазі вбудований текст у C ++, тому використання параметра USE_CONSTEXPR генерує лише клас заголовка!

Приклад:

./includes/ah:

enum AAA : char { A1, A2 };

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

./CMakeLists.txt:

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

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

Створює:

./enum2Str.hpp:

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

#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP

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

namespace abc {

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

}

#endif // ENUM2STR_HPP

./enum2Str.cpp:

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

#include "enum2Str.hpp"

namespace abc {

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

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

Оновлення:

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


Оце Так! Дуже оригінальна та новаторська ідея :-) Я сподіваюся, що у вас є сміливість модернізувати свій генератор, щоб забезпечити версію constexprта noexceptверсію ;-) Я також щойно дивився на ваш проект GitHub ;-) Ура
олибре

1
Оновлено генератор. Тепер функції завжди будуть constexpr та enum: <type> тепер підтримується. Дякую за зірку :)
Mense

Посилання розірвано ... -.-
yeoman

Зараз посилання виправлено.
Mense

4

Просто генеруйте свої перерахунки. Написати генератор для цієї мети - це близько п'яти хвилин роботи.

Генераторний код у java та python, надзвичайно простий в порту на будь-яку мову, яка вам подобається, включаючи C ++.

Також супер легко розширити будь-яку функціональність, яку ви хочете.

Приклад введення:

First = 5
Second
Third = 7
Fourth
Fifth=11

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

#include <iosfwd>

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

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

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

#include <ostream>

#include "Hallo.h"

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

    return out;
}

І генератор, у дуже лаконічній формі як шаблон для перенесення та розширення. Цей приклад код дійсно намагається уникнути перезапису будь-яких файлів, але все-таки використовувати його на свій страх і ризик.

package cppgen;

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

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

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

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

        String enumName = args[0];

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

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

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

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

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

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

        int nextValue = 0;

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

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

                if (line == null)
                {
                    break;
                }

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

                Matcher matcher = pattern.matcher(line);

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

                String fieldName = matcher.group(1);

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

                String valueString = matcher.group(2);

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

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

                    nextValue = value;
                }

                fields.put(fieldName, nextValue);

                ++nextValue;
            }
        }

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

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

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

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

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

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

І порт на Python 3.5, тому що досить різний, щоб бути потенційно корисним

import re
import collections
import sys
import io
import os

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

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

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

enumName = sys.argv[1]

inputFile = sys.argv[2]

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

outputDir = sys.argv[3]

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

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

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

nextValue = 0

fields = collections.OrderedDict()

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

    if len(line) == 0:
        continue

    match = pattern.match(line)

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

    fieldName = match.group(1)

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

    valueString = match.group(2)

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

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

        nextValue = value

    fields[fieldName] = nextValue

    nextValue += 1

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

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

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

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

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

1
Дякую вам за те, що поділився генератором двома мовами :-) Але ви маєте ідею, як генерувати під час компіляції? Наприклад, чи можемо ми уявити переклад генератора за допомогою операторів CMake для того, щоб оновити код C ++, що генерується при зміні вхідних даних? Моя мрія - змусити компілятор C ++ генерувати перерахунки при компіляції за допомогою метапрограмування ( variadic template classі constexprфункцій).
олібре

Ото, якщо ви занадто громіздко додати користувальницьку команду cmake, ви можете автоматизувати свій IDE або зателефонувати в gererator вручну та мати вихід у керуванні джерелом. Іноді корисно все-таки створити код у керуванні джерелом, якщо це не надто багато, і люди розуміють, що вони не повинні вносити вручну зміни, оскільки іноді цікаво подивитися історію створених файлів, коли ви ви налагоджуєте щось дивне і підозрюєте, що нещодавно змінилася генератор щось
зламало

Щодо генерації речей під час компіляції, це так просто в LISP, оскільки синтаксис надзвичайно чистий і простий. Цьому допомагає той факт, що він динамічно набраний, що дозволяє йому бути лаконічним і читабельним без особливих синтаксисів. Еквівалент макросів LISP в C ++ потребує дуже складного способу описати AST того, що ви намагаєтеся генерувати. І AST для C ++ ніколи не є гарним :(
yeoman

Прямо в Make замість cmake це дуже просто btw. Просто генеруйте .h та .cpp-цілі для кожного .enum-файлу через find, і ці цілі залежать від вказаних переліку defs, тож вони автоматично повторно генеруються після зміни файлів .enum def. Це, мабуть, набагато простіше в cmake, тому що він сповнений магії для подібних речей, але я регулярно використовую Make, ant, and gradele, але маю обмежені знання про Maven, cmake та grunt :)
yeoman

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

3

Відповідно до запиту від ОП, тут знята версія потворного макроресурсу на основі Boost Preprosessor та Variadic Macros .

Це дозволяє простий список, як синтаксис елементів перелічувача, а також встановлення значень для конкретних елементів, так що

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

розширюється до

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

Поряд з необхідними функціями для виведення і здійснення певного перетворення назад. Цей макрос існує тут уже віками, і я не зовсім впевнений, що це найефективніший спосіб, або що це відповідний спосіб, але він з тих пір працює

Повний код можна побачити як у Ideone, так і в Coliru .

Її гаргантуальне потворство вище; Я б поставив це за спойлери, щоб захистити ваші очі, якби я знав як, але розмітка не подобається мені.

Бібліотека (об'єднана в один файл заголовка)

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

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

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

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

} // namespace xxx

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

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

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

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

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

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

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

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

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

Використання

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

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

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

Компіляція (скопіюйте заголовок вставки всередині main.cpp)

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

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

Вихідні дані

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

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

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

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

Гаразд, плазма, я вважаю, що це можна розглядати як доказ концепції . Але є занадто багато макроресурсів, щоб їх можна було проголосувати. Тим не менше дякую за обмін. Ура
олібре

Привіт плазма. Я здійснив глибоке очищення вихідного коду + завершено компіляцією та запуском виводу. Будь ласка, перевірте мою редакцію . Я сподіваюся, що це нормально для вас. Чи є відповідь ціннішою? Однак макро накладні все ще жахливі! Приємного дня :-)
Привіт

2

Наступне рішення засноване на a std::array<std::string,N> заданій перерахунку.

Для enumдля std::stringперетворення ми можемо просто кинути перерахування для size_tі пошуку рядка з масиву. Операція є O (1) і не вимагає виділення купи.

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

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

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

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

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

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

Редагувати: Переробляв моє рішення, щоб користувацький Enum можна було використовувати в класі.


Дякую за цікаву відповідь. Будь ласка, переробіть свою пропозицію, щоб використовувати ваш макрос у класі. Дивіться інформацію про coliru.stacked-crooked.com/a/00d362eba836d04b Крім того, спробуйте використовувати constexprта noexeptключові слова, коли це можливо. Ура :-)
олібре

Питання не вказувало цього реквізиту.
FKaria

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

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

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

2

Ця суть забезпечує просте відображення на основі шаблонів різноманітних C ++.

Це C ++ 17-спрощена версія типової карти з суті :

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

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

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

Приклад використання:

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

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

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

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

map<KeyValues...>Можна використовувати в обох напрямках:

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

Цей приклад доступний на godbolt.org

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

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

main:
        mov     eax, 3
        ret

1
Дуже цікавий спосіб програмування мета. Я спробував трохи спростити відповідь, щоб бути автономним (без залежності від посилання Gist). Щоб бути лаконічним і зрозумілим, я нарешті багато відредагував вашу відповідь. Чи ви згодні з моїми змінами? Ура ;-)
олібре

2

Мене вже давно засмучує ця проблема, а також проблема правильного перетворення типу в рядок. Однак для останньої проблеми мене здивувало рішення, пояснене в розділі " Чи можливо надрукувати тип змінної у стандартному C ++?" , використовуючи ідею від Чи можу я отримати назви типів C ++ у вигляді конвекспресу? . За допомогою цієї методики аналогічна функція може бути побудована для отримання значення enum у вигляді рядка:

#include <iostream>
using namespace std;

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

public:
    typedef const char* const_iterator;

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

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

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

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

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

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

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

}

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

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

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


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

Код, описаний вище, був протестований лише на Clang (див. Https://ideone.com/je5Quv ) та VS2015, але повинен бути адаптований до інших компіляторів, трохи позначившись із цілими константами. Звичайно, він все ще використовує макроси під кришкою, але принаймні одному не потрібен доступ до реалізації enum.


Це не вдається з g ++ 6.3.0 та C ++ 14.
einpoklum

Цікаво тим, що перерахунок може бути оголошений нормально без необхідності загортати його в макрос. Хоча мені не подобаються компіляторські залежності та магічні константи.
zett42

2

Я взяв ідею від @antron і реалізував її інакше: генеруючи правду клас enum .

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

Це не суттєве обмеження - просто те, що я не використовую спеціальні значення перерахунку. Якщо це потрібно, можна замінити векторний пошук традиційною реалізацією комутатора / випадку.

Рішення використовує деякі c ++ 17 для вбудованих змінних, але цього можна легко уникнути, якщо потрібно. Він також використовує boost: обробку через простоту.

Найголовніше, що воно займає лише 30 рядків коду і ніяких макросів чорної магії. Код нижче. Він призначений для заголовка та включення до декількох модулів компіляції.

Його можна використовувати так само, як було запропоновано раніше в цій темі:

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

Будь ласка, дайте мені знати, чи це корисно та як це можна вдосконалити далі.


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

2

Поки ви не хочете писати окрему .h/.cppпару для кожного перераховуваного перерахунку, це рішення працює з майже таким же синтаксисом та можливостями, як і звичайний c ++ перерахунок:

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

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

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

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

Приклад використання:

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

Код

Для цього рішення потрібні 2 вихідні файли:

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

#define ETRAITS
#define EDECL(x) x

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

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

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

public:
    EnumTraits() = delete;

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

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

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

... і

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

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

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

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

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

Пояснення

Ця реалізація використовує той факт, що скобований список елементів визначення enum також може використовуватися як список брекет-ініціалізаторів для ініціалізації членів класу.

Коли ETRAITSоцінюється в контексті EnumTraits.inl, воно розширюється до статичного визначення члена дляEnumTraits<> класу.

У EDECLмакро перетворення кожного члена перерахування до списку Initializer значень , які згодом отримують передані в конструктор члена для того , щоб заповнити дані перерахування.

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

Переваги

  • c++-подібний синтаксис
  • Працює однаково для обох enumі enum class(* майже)
  • Працює для enumтипів із будь-яким числовим базовим типом
  • Працює для enumтипів з автоматичними, явними та фрагментованими значеннями ініціалізатора
  • Працює для масового перейменування (збережене міжбільшеве посилання)
  • Лише 5 символів препроцесора (3 глобальних)

* На відміну від enumsініціалізаторів enum classтипів, на які посилаються інші значення з того ж переліку, повинні мати ці значення повністю кваліфіковані

Недоліки

  • Потрібна окрема .h/.cppпара для кожного запитуваногоenum
  • Залежить від звивистості macroта includeмагії
  • Незначні синтаксичні помилки вибухають у набагато більші помилки
  • Визначення classчи namespaceобсяг перерахунків нетривіально
  • Немає ініціалізації часу компіляції

Коментарі

Intellisense трохи поскаржиться на доступ приватного члена при відкритті EnumTraits.inl, але оскільки розширені макроси насправді визначають членів класу, це насправді не є проблемою.

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

При оголошенні переліку областей простору імен enum спочатку повинен бути оголошений у межах своєї області імен, а потім визначений у глобальному просторі імен. Крім того, будь-які ініціалізатори enum, що використовують значення одного і того ж enum, повинні мати ці значення повністю кваліфікованими.

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

2

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

Те, що я хочу включити, є еквівалентом

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

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

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

який повинен друкувати

ONE
TWO
13

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

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

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

Більшість це визначальні та невизначені символи, які користувач передасть як параметр X-marco через включення. Використання таке

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

Демонстраційна демонстрація

Зауважте, що я ще не включав вибір базового типу. Мені цього не потрібно, але слід змінити код, щоб це ввімкнути.

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


1

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

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 або type) полягає в тому, щоб:

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

Це дуже цікаво :-) Однак ваша поточна версія передбачає, що ви повинні вручну писати матеріали case Enum::RED: return "red";. Питання полягає в автоматизації цього компілятора (під час компіляції). Ідея питання полягає лише в тому, щоб змінити або додати значення перерахунків, не потребуючи оновлення матеріалів toString(). Ви бачите? Спасибі
олібре

1

Дуже просте рішення з одним великим обмеженням: ви не можете призначати власні значення enumзначенням, але за допомогою правильного регулярного вираження ви могли. Ви також можете додати карту, щоб перевести їх назад до enumзначень без особливих зусиль:

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

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

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

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

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

Приклад використання:

EnumToString(MyEnum, Red, Green, Blue);

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

Мені дуже подобається такий підхід. Дійсно короткий і легкий для розуміння.
Антон Голмберг

1

РЕДАКТУВАННЯ: перевірте, чи не з’явилася нова версія

Як було сказано вище, N4113 - остаточне рішення цього питання, але нам доведеться почекати більше року, щоб побачити, як він вийде.

Тим часом, якщо вам потрібна така функція, вам доведеться вдатися до "простих" шаблонів і деякої магії препроцесора.

Перелік

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

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

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

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

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

Використання

#include <iostream>

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

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

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

Просте пояснення

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

Недоліки

  • Це не справжній enumтип, тому не можна рекламувати до 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;
}

Реалізація та використання в реальному житті

r3dVoxel - Enum
r3dVoxel - ELoggingLevel

Короткий довідник

#line lineno - cppreference.com


0

Я написав бібліотеку для вирішення цієї проблеми, все відбувається за час збирання, за винятком отримання повідомлення.

Використання:

Використовуйте макрос DEF_MSGдля визначення пари макросів та повідомлень:

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

CODE_OK - макрос, який слід використовувати, і "OK!" є відповідним повідомленням.

Використовуйте get_message()або просто, gm()щоб отримати повідомлення:

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

Використовуйте, MSG_NUMщоб дізнатись, скільки макросів було визначено. Це автоматично збільшується, вам нічого не потрібно робити.

Попередні повідомлення:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

Проект: libcodemsg


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

У такому випадку бібліотека обмежується лише створенням однієї enum. Це ідеально для повернених значень, наприклад:

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

MSG_CODE ret = foo();

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

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


Я знайшов, що рішення цього питання виглядає набагато краще.


Привіт, Мадвін. Дякую за вашу ідею. Але як це працює? Що таке накладні витрати? (нульові накладні витрати чи це створює додаткові дані?). Ваша пропозиція здається прекрасною, але, на жаль, DEF_MSGдля кожного enumзначення слід використовувати / оновлювати / підтримувати одне твердження : - / І саме це в ідеалі ми хотіли б перестати робити ... Ура
олибре

Дякую за відповідь, @olibre. Перевірте оновлену відповідь. Я не бачу тут зверху, за винятком виклику функції для доступу до рядків. DEF_MSGробить enumтісно зв'язане з повідомленням, хоча воно має деякі обмеження.
Мадвін

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

1
Дякуємо за наступні дії. Я дізнався сьогодні щось нове! Змінні класу enum та шаблони добре виглядають. Я думаю, що моя відповідь була трохи «поза темою», оскільки вона була ароматизована С.
Мадвін

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


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

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

        unsigned int numSplits = 0;

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

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

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

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



        return ret;
    }

приклад

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


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

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

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

автоматично ENUM_MAKE макрогенерує 'enum class' та helper class з 'функцією відображення enum'.

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

Перевага цього коду створюється автоматично для відображення та пильного ознайомлення з макрокодом, легко зрозумілим кодом. виконання 'enum to string', 'string to enum' є алгоритмом O (1).

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


Хоча цей код може відповісти на питання, було б краще пояснити, як він вирішує проблему, не вводячи інших і навіщо її використовувати. Відповіді, що стосуються лише коду, не є корисними в довгостроковій перспективі.
JAL

Ей, хлопці, мені шкода, що я не дуже добре розмовляю англійською
desperado_98

автоматично ENUM_MAKE макрогенерує 'enum class' та helper class з 'функцією відображення enum'. / Щоб зменшити помилки, відразу все визначається лише одним ENUM_MAKE. Перевага цього коду створюється автоматично для відображення та пильного ознайомлення з макрокодом, легко зрозумілим кодом. продуктивність 'enum to string', 'string to enum' є алгоритмом O (1). Недоліки полягають у тому, що при першому використанні ініціалізується хелперний клас для векторного рядка та карти відбивання перерахунків. але якщо ви хочете, ви також будете попередньо ініціалізовані.
desperado_98

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

Більше того: чи сумісний ваш макрос enum class MyEnum : short { A, B, C };?
олібре

0

моє рішення - без використання макросу.

переваги:

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

недоліки:

  • вам потрібно повторити всі значення перерахунків у вигляді тексту
  • доступ до хеш-карти повинен враховувати регістр рядків
  • обслуговування, якщо додавання значень болісно - потрібно додати як перерахунок, так і пряму карту перекладу

так що ... до дня, коли C ++ реалізує функцію C # Enum.Parse, я буду застряг у цьому:

            #include <unordered_map>

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

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

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

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

                static bool m_init_done;
            };

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

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

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

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

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

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

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

0

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

Приклад:

int main () {

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


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

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

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

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

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

}

Клас VERB:

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

private:

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

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

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

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

public:

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

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

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

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

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

  // ---

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

};

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

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

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

0

Моя відповідь тут.

Ви можете отримати імена значень переліку та ці індекси одночасно як deque of string.

Цей метод потребує лише копіювання та вставки та редагування.

Отриманий результат потребує кастингу типу від size_t до типу enum, коли вам потрібно значення типу enum class, але я думаю, що це дуже портативний і потужний спосіб поводження з enum class.

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

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

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

0

Ви можете використовувати бібліотеку рефлексій, наприклад Ponder :

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

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

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

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

0

(Аналог https://stackoverflow.com/a/54967187/2338477 , трохи змінений).

Ось моє власне рішення з мінімальним визначенням магії та підтримкою окремих завдань перерахувань.

Ось файл заголовка:

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

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

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

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

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

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

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

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

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

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


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

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

    From string to enumeration:

       enumName value;

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

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

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

    return it->second;
}

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

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

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

Ось приклад тестової програми:

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

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

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

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

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

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

Тут буде збережена оновлена ​​версія того ж файлу заголовка:

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


-5

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

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

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

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

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

5
1) це створює ще більше дублювання 2) воно змушує вас використовувати потоки
Каролі Хорват

6
-1 Вибачте @dau_sama, але мета всіх цих перерахувань до рядкових повторюваних питань полягає у тому, щоб уникнути підтримки переліку enum / string. Якщо ви вважаєте, що ваша відповідь не відповідає меті, будь ласка, подумайте про видалення відповіді. Удачі у вашій наступній відповіді;)
Привіт

-9

Найпростіший спосіб?
Використовуйте Ада: Enumeration'Image( Value )робить саме те, що ви хочете. Якщо вам справді потрібен C ++, ви можете спробувати експортувати функцію:

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

4
Як це взагалі відповідає на питання? Питання чітко визначає перетворення enum у рядок у сучасному C ++.
Майкл Чой

1
@MichaelChoi - Це так, але також існує проблема використання відповідного інструменту для роботи: тільки тому, що C ++ є цілком завершеним і тому може вирішити всі вирішувані проблеми НЕ означає, що рішення є: швидким, доступним або ефективним. Використання мови, яка має належну / бажану функціональність, та експортування її є правильним рішенням.
акула8

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