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

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

  • 2008 c есть ли простой способ конвертировать перечисление C++ в строку?
  • 2008 c простой способ использовать переменные типов перечислений в качестве строки в C?
  • 2008 c++ как легко сопоставить перечисления c++ со строками
  • 2008 c++ что-то как идентификатор C и строку?
  • 2008 c++ есть ли простой скрипт для преобразования перечисления C++ в строку?
  • 2009 c++ как использовать перечисления в качестве флагов в C++?
  • c++ как преобразовать переменную типа перечисления в строку?
  • c++ перечисление в строку C++
  • c++ как преобразовать переменную типа перечисления в строку?
  • 2012 c как преобразовать имена перечислений в строку в c
  • c Stringifying условно скомпилированное перечисление в 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)
  • noexcept (C++11)
  • фрагмент кода на C++14 и C++17-фрэндли
  • C++ современное состояние

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

24 ответов


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

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

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

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

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

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

код:

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

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

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

    std::stringstream stringStream(str);

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

    return vecString;
}

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

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

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

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

    return retMap;
}

пример:

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

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

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

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

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

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

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


(подход 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.

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

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

  • основная часть этого ответа-это реализация, которая, я думаю, подходит для ограничений пространства на StackOverflow.
  • также статья CodeProject описание основ реализации в учебнике длинной формы. [должен ли я переместить его сюда? Я думаю, это слишком. много за такой ответ].
  • есть полнофункциональная библиотека "лучшие перечисления", который реализует макрос в одном файле заголовка. Он также реализует 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 ''.
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] == '' :
        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] = '';                      \
            }                                                         \
                                                                      \
            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" был преобразован в перечисление во время компиляции.


на 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>
  >

например (взято из Matus Choclik's reflexpr филиал clang):

#include <reflexpr>
#include <iostream>

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

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

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

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

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


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

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

макросы Vim веселее, чем макросы C++.

реальный пример:

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

Я создам этот:

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

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

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

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


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

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

хорошие моменты об этом aproach:

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

видео

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

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

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

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

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

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

затем, изменчивый шаблон обмана:

template <typename ENUM>
void initialize() {}

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

в "лучшие трик!--47--> " вот использование шаблона переменной для карты, которая содержит значения и имена каждой записи перечисления; эта карта будет одинаковой в каждой единице перевода и иметь одинаковое имя везде, поэтому довольно просто и аккуратно, если мы назовем видео


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


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

Примечание: constexpr подразумевает встроенный в C++, поэтому с помощью опции USE_CONSTEXPR будет генерировать только класс заголовка!

пример:

./включает в себя/а.ч:

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.ГЭС:

/*!
  * \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


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

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

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

увеличивается до

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

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

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

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

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

#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

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

генератор кода на 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()

Я взял идею из @antron и реализовал ее по-другому: создание true перечислимый класс.

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

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

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

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

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

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

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


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

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

#include <iostream>
using namespace std;

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

public:
    typedef const char* const_iterator;

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

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

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

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

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

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

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

}

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

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

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


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

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


пока вы в порядке с написанием отдельного .h/.cpp pair для каждого запрашиваемого перечисления это решение работает с почти таким же синтаксисом и возможностями, как и обычное перечисление 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
};

на это 3 строк шаблонного:

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

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

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

код

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

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

#define ETRAITS
#define EDECL(x) x

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

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

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

public:
    EnumTraits() = delete;

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

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

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

...и

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

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

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

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

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

объяснение

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

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

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

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

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

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

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

Disbenefits

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

комментарии

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

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

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

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

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

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

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

Edit: переработал мое решение, чтобы пользовательское перечисление можно было использовать внутри класса.


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

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". Возможно, вы можете перегрузить

цель неявное преобразование в перечисление (которое неявно преобразуется в int или заданный тип) должно быть в состоянии сделать:

Color color;
switch (color) ...

это работает, но это также означает, что эта работа слишком:

int i = color;

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

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

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

    //same as previous...
};

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

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

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

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

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

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

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

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

этой суть обеспечивает простое сопоставление на основе C++ variadic шаблонов.

это 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;

The 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

очень простое решение с одним большим ограничением: вы не можете назначить пользовательские значения 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);

EDIT: проверьте ниже для более новой версии

как уже упоминалось выше, 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 - перечисление
r3dVoxel-ELoggingLevel

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

# line lineno -- cppreference.com


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

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

использовать макрос 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; in 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););
}

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


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


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


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

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

        unsigned int numSplits = 0;

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

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

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

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



        return ret;
    }

пример

ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)


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

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

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

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

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

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

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


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

плюсы:

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

недостатки:

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

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

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

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

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

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

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

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

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

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"

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

}

класс глаголов:

// -----------------------------------------------------------
// -----------------------------------------------------------
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

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

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