关于c ++:enum to string in modern C ++ 11 / C ++ 14 / C ++ 17和future C ++ 20

enum to string in modern C++11 / C++14 / C++17 and future C++20

与所有其他类似的问题相反,这个问题是关于使用新的C++特征。

  • 2008 C有一种简单的方法将C++枚举转换成字符串吗?
  • 2008 c在c中使用枚举类型的变量作为字符串的简单方法?
  • 2008 C++如何轻松地将C++枚举映射到字符串
  • 2008 C++既有C标识符又有字符串?
  • 2008 C++有一个简单的脚本把C++枚举转换成字符串吗?
  • 2009 C++如何在C++中使用枚举作为标志?
  • 2011 C++如何将枚举类型变量转换为字符串?
  • 2011 C++EnUM到String C++
  • 2011 C++如何将枚举类型变量转换为字符串?
  • 2012 c如何在c中将枚举名称转换为字符串
  • 2013 c在c中对有条件编译的枚举进行字符串化

在阅读了许多答案之后,我还没有找到:

  • 优雅的方式使用C++ 11、C++ 14或C++ 17的新特性
  • 或者准备用来增强的东西
  • 否则为C++ 20设计的东西

例子

一个例子往往比一个冗长的解释好。您可以在coliru上编译和运行这个片段。(另一个以前的示例也可用)

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
#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) <<'
'
;
   std::cout << magic(MyClass::MyEnum::BBB) <<'
'
;
   std::cout << magic(MyClass::MyEnum::CCC) <<'
'
;
}

约束条件

  • 请不要重复其他答案或基本链接。
  • 请避免基于宏的答案膨胀,或尽量减少#define的开销。
  • 请不要手动进行enum->string映射。

很高兴

  • 支持从零以外的数字开始的enum
  • 支持负enum
  • 支持零碎的enum
  • 支持class enum(C++ 11)
  • 支持class enum : 具有任何允许的(C++11)
  • 编译时(非运行时)转换为字符串,或者至少在运行时快速执行(例如,std::map不是一个好主意…)
  • EDCOX1〔10〕(C++ 11,在C++ 14中放宽)
  • noexcept(C++11)
  • 代码14 + C++ 17友好
  • C++的艺术状态

一种可能的想法是使用C++编译器能力在编译时生成基于C++的EDCOX1 12和EDCX1…10函数的元编程技巧。


(The approach of the better_Enums library)好的。

有一种方法在EnUM中对当前的C++进行字符串处理:好的。

1
2
3
4
ENUM(Channel, char, Red = 1, Green, Blue)

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

用途:好的。

1
2
3
4
5
6
7
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...

所有的操作都可以进行。您还可以通过@ eCuMUR实现答案中提到的C++ 17反射方案。好的。

  • 只有一个宏。我相信这是最小可能的,因为预处理器String化(EDCOX1(1))是在当前C++中将令牌转换为字符串的唯一方法。
  • 宏非常不引人注目——常量声明(包括初始值设定项)被粘贴到内置的枚举声明中。这意味着它们具有与内置枚举相同的语法和含义。
  • 重复被消除。
  • 由于EDCOX1,0,所以实现至少在C++ 11中是最自然和最有用的。它也可以用C++ 98 +EDCOX1 3来工作。它绝对是现代C++。

宏的定义有点复杂,所以我用几种方式来回答这个问题。好的。

  • 这个答案的主要部分是一个我认为适合stackoverflow上空间约束的实现。
  • 还有一篇代码项目文章描述了在长格式教程中实现的基础知识。[我应该把它移到这里吗?我觉得这样的回答太过分了。
  • 有一个功能齐全的库"BetterEnums",它在单个头文件中实现宏。它还实现了N428类型属性查询,当前修订了C++ 17的反射方案N4113。因此,至少对于通过这个宏声明的枚举,现在可以在C++ 11/C++ 14中有提议的C++ 17枚举反射。

将这个答案扩展到库的特性是很简单的——这里没有遗漏任何"重要"的内容。然而,它相当冗长,并且存在编译器可移植性问题。好的。

免责声明:我是代码项目文章和库的作者。好的。

您可以尝试此答案中的代码、库以及在Wandbox中在线实现N4428。图书馆文档还包含如何将其用作N4428的概述,这解释了该建议的Enums部分。好的。解释

下面的代码实现枚举和字符串之间的转换。但是,它也可以扩展到做其他事情,比如迭代。此答案将枚举包装在struct中。您还可以在枚举旁边生成特性struct。好的。

策略是生成如下内容:好的。

1
2
3
4
5
6
7
8
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 */ }
};

问题是:好的。

  • 最后我们将使用类似于{Red = 1, Green, Blue}的东西作为值数组的初始值设定项。这是无效的C++,因为EDCOX1〔1〕不是一个可赋值的表达式。这可以通过将每个常量强制转换为具有赋值运算符的T类型来解决,但会删除赋值:{(T)Red = 1, (T)Green, (T)Blue}
  • 同样,我们将以{"Red = 1","Green","Blue"}作为名称数组的初始值设定项结束。我们需要把" = 1"剪掉。我不知道在编译时这样做的好方法,所以我们将把它推迟到运行时。因此,_to_string不会是constexpr,但_from_string仍然可以是constexpr,因为与未经修剪的字符串相比,我们可以将空格和等号视为终止符。
  • 以上两种方法都需要一个"mapping"宏,可以对__VA_ARGS__中的每个元素应用另一个宏。这是相当标准的。这个答案包括一个简单的版本,可以处理多达8个元素。
  • 如果宏要真正独立,则需要声明不需要单独定义的静态数据。实际上,这意味着阵列需要特殊处理。有两种可能的解决方案:命名空间范围内的constexpr数组(或仅const数组)或非constexpr静态内联函数中的常规数组。这个答案的代码是C++ 11,并采用前一种方法。代码项目文章是针对C++ 98的,并采用后者。
  • 代码

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    #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

    "
    ;

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

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



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

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

    和好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 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"在编译期间被转换为枚举。好的。好啊。


    对于< Case> C++ 17 C++ 20,您将对反射学习小组(SG7)的工作感兴趣。有一系列平行的论文涵盖了措辞(p194)和理论基础、设计和发展(p0385)。(链接解析为每个系列中的最新纸张。)

    截至p194r2(2016-10-15),语法将使用建议的reflexpr关键字:

    1
    2
    3
    4
    5
    meta::get_base_name_v<
      meta::get_element_m<
        meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
      >

    例如(改编自Matus Choclik的clang reflexpr分支):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #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年11月在Issaquah召开的标准会议的最后草稿),但有信心它将进入C++ 20;从赫伯-萨特的旅行报告:

    In particular, the Reflection study group reviewed the latest merged static reflection proposal and found it ready to enter the main Evolution groups at our next meeting to start considering the unified static reflection proposal for a TS or for the next standard.


    这与YuriFinkelstein相似,但不需要提升。我正在使用一个映射,这样您就可以为枚举分配任何值,任何顺序。

    枚举类声明为:

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

    以下代码将自动创建枚举类和重载:

    • 用于STD::字符串
    • 流的"<<"
    • "~"只是转换为字符串(任何一元运算符都可以,但我个人不喜欢这样做,因为这很清楚)
    • "*"以获取枚举数

    无需增压,提供所有所需功能。

    代码:

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    #include
    #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;
    }

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    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;
    }

    您可以在此处运行代码


    回到2011年,我花了一个周末的时间对一个基于宏的解决方案进行微调,结果却没有使用它。

    我当前的过程是启动vim,在一个空开关体中复制枚举器,启动一个新宏,将第一个枚举器转换为case语句,将光标移动到下一行的开头,停止宏,并通过在其他枚举器上运行宏生成其余case语句。

    VIM宏比C++宏更有趣。

    现实生活示例:

    1
    2
    3
    4
    5
    6
    7
    enum class EtherType : uint16_t
    {
        ARP   = 0x0806,
        IPv4  = 0x0800,
        VLAN  = 0x8100,
        IPv6  = 0x86DD
    };

    我将创建此:

    10

    我就是这样度过的。

    不过,对枚举字符串化的本地支持要好得多。我非常感兴趣的是C++ 17中反射工作组的结果。

    @sehe在评论中发布了一种替代方法。


    我不知道你是否会喜欢这个,我不满意这个解决方案,但它是一个C++ 14友好的方法,因为它使用模板变量和滥用模板专业化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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> << '
    '
    ;
        // Prints"Invalid MyEnum value"
        std::cout << MyEnumName<static_cast<MyEnum>(0x12345678)> << '
    '
    ;
        // Well... in fact it prints"Invalid MyEnum value" for any value
        // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.

        return 0;
    }

    这种方法最糟糕的是,要保持这种状态很痛苦,但要保持一些其他类似的围裙也很痛苦,不是吗?

    关于这个问题的好观点:

    • 使用变量TimeTATE(C++ 14特征)
    • 使用模板专门化,我们可以"检测"何时使用了无效的值(但我不确定这是否有用)。
    • 看起来很整洁。
    • 名称查找在编译时完成。

    LIve示例

    编辑

    错误的用户67 3679你是对的,C++ 14变量模板方法不处理运行时的情况,忘记它是我的错:

    但是我们仍然可以使用一些现代的C++特性和可变模板加上可变模板欺骗来实现从枚举值到字符串的运行时转换。这和其他人一样麻烦,但仍然值得一提。

    让我们开始使用模板别名来缩短对枚举到字符串映射的访问:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 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{};

    然后,变量模板欺骗:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    template <typename ENUM>
    void initialize() {}

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

    这里的"最佳技巧"是为包含每个枚举项的值和名称的映射使用变量模板;如果我们这样调用initialize函数,那么这个映射在每个翻译单元中都是相同的,并且在任何地方都具有相同的名称,因此非常简单和整洁:

    1
    2
    3
    4
    5
    6
    initialize
    (
        MyEnum::AAA,"AAA",
        MyEnum::BBB,"BBB",
        MyEnum::CCC,"CCC"
    );

    我们为每个MyEnum条目指定名称,并且可以在运行时使用:

    1
    2
    std::cout << enum_values<MyEnum>[MyEnum::AAA] << '
    '
    ;

    但可以使用sfinae和重载<<运算符进行改进:

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

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

    使用正确的operator <<,现在我们可以这样使用枚举:

    1
    2
    std::cout << MyEnum::AAA << '
    '
    ;

    这也很麻烦维护和可以改进,但希望你得到这个想法。

    LIve示例


    如果你的enum看起来像

    1
    2
    3
    4
    5
    6
    enum MyEnum
    {
      AAA = -8,
      BBB = '8',
      CCC = AAA + BBB
    };

    您可以将enum的内容移动到新文件中:

    1
    2
    3
    AAA = -8,
    BBB = '8',
    CCC = AAA + BBB

    然后这些值可以被宏包围:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 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中的项目:

    1
    2
    3
    4
    5
    enum MyEnum
    {
      #define ITEM(X,Y) X=Y,
      #include"enum_definition_file"
    };

    最后,您可以生成关于这个enum的实用程序函数:

    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
    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++解决方案,所以我决定编写一个CGED代码生成器来生成简单的Switter case语句。

    用途:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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_directories命令提供的include目录),读取它们并执行一些regex来生成类和函数。

    注意:CONTXPR表示C++中的内联,因此使用USEL CONTHEXPRO选项将生成一个头类类!

    例子:

    /包括/A.H:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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:

    10

    生成:

    /枚举2HPP:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /*!
      * \file enum2Str.hpp
      * \warning This is an automatically generated file!
      */


    #ifndef ENUM2STR_HPP
    #define ENUM2STR_HPP

    #include <string>
    #include

    namespace abc {

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

    }

    #endif // ENUM2STR_HPP

    /NOD2STR.CPP:

    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
    /*!
      * \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
     *
    eturns _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
     *
    eturns _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>";
       }
    }
    }

    更新:

    脚本现在还支持范围枚举(枚举类结构)和我把它和一些我经常使用的其他脚本(https://github.com/mensinda/cmakebuildtools)一起转移到了一个单独的报告中。


    只需生成枚举。为此编写一个生成器大约需要5分钟的时间。

    Java和Python中的生成器代码,非常容易移植到你喜欢的任何语言,包括C++。

    此外,无论您想要什么功能,都非常容易扩展。

    实例输入:

    1
    2
    3
    4
    5
    First = 5
    Second
    Third = 7
    Fourth
    Fifth=11

    生成的标题:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <iosfwd>

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

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

    生成的cpp文件

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

    以及生成器,以非常简洁的形式作为移植和扩展的模板。此示例代码确实试图避免覆盖任何文件,但仍要使用它,风险由您自己承担。

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    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的端口,因为它的不同足以提供潜在的帮助

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    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("
    "
    )
        headerWriter.write("#include <iosfwd>
    "
    )
        headerWriter.write("
    "
    )
        headerWriter.write("enum class" + enumName +"
    "
    )
        headerWriter.write("{
    "
    )
        first = True
        for fieldName, fieldValue in fields.items():
            if not first:
                headerWriter.write(",
    "
    )

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

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

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


    根据OP的要求,这里有一个基于BoostPreassessor和variadic宏的丑陋宏解决方案的简化版本。

    它允许枚举器元素的简单列表式语法以及为特定元素设置值,以便

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

    扩展到

    1
    2
    3
    4
    5
    enum foo {
        a,
        b,
        c=42
    };

    与必要的函数一起输出并进行一些转换。这个宏已经存在很久了,我不完全确定它是最有效的方法,或者说它是一种一致的方法,但它从那时起就一直在工作。

    完整的代码可以在IDeone和Coliru上看到。

    它的巨大丑陋就在上面;如果我知道的话,我会把它放在扰流器后面来保护你的眼睛,但是Markdown不喜欢我。

    库(合并到单个头文件中)

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    #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)

    用法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #include"xxx_enum.h"  // the above lib
    #include <iostream>

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

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

    编译(在main.cpp中复制粘贴头)

    1
    2
    3
    4
    5
    6
    7
    > 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)));
                                   ^

    产量

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


    我从@antron获得了这个想法,并以不同的方式实现了它:生成一个真正的枚举类。

    此实现满足原始问题中列出的所有要求,但当前只有一个真正的限制:它假定没有提供枚举值,或者如果提供了枚举值,则必须从0开始,并按顺序递增,没有间隙。

    这不是一个内在的限制——只是我不使用特殊的枚举值。如果需要,可以用传统的开关/案例实现替换矢量查找。

    该解决方案使用一些C++ 17用于内联变量,但是如果需要的话,这可以很容易避免。它还使用了Boost:Trim,因为它简单。

    最重要的是,它只需要30行代码,不需要黑色的魔法宏。代码如下。它应该放在头中并包含在多个编译模块中。

    它的使用方法与本文前面建议的方法相同:

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

    请告诉我这是否有用以及如何进一步改进。

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

    我不确定这种方法是否已经包含在其他答案中(实际上是,见下文)。我多次遇到这个问题,没有找到不使用模糊宏或第三方库的解决方案。因此,我决定编写自己模糊的宏版本。

    我要启用的是

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    enum class test1 { ONE, TWO = 13, SIX };

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

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

    哪个应该打印

    1
    2
    3
    ONE
    TWO
    13

    我不喜欢宏。但是,除非C++本身支持将枚举转换为字符串,否则必须使用某种代码生成和/或宏(并且我怀疑这种情况会发生得太快)。我使用的是X宏:

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

    #define x_begin inline std::string toString(const x_name& e) { \
                    static std::map<x_name,std::string> names = {

    #define x_val(X)      { x_name::X , #X }
    #define x_value(X,Y)  { x_name::X , #X }
    #define x_end }; return names[e]; }
    x_enum_def
    #undef x_begin
    #undef x_val
    #undef x_value
    #undef x_end
    #undef x_name
    #undef x_enum_def

    其中大部分是定义和取消定义符号,用户将通过include作为参数传递给x-marco。用法是这样的

    1
    2
    3
    4
    5
    6
    #define x_name test1
    #define x_enum_def x_begin x_val(ONE) , \
                               x_value(TWO,13) , \
                               x_val(SIX) \
                       x_end

    #include"x_enum.h"

    现场演示

    注意,我还没有包括选择底层类型。到目前为止我还不需要它,但是应该直接修改代码来实现它。

    只有在写了这篇文章之后,我才意识到这和艾弗里昂的回答很相似。也许我以前读过,也许它是灵感的主要来源。在编写自己的x宏之前,我一直无法理解x宏;)。


    只要您可以为每个可查询枚举编写一个单独的EDCOX1×0对,该解决方案与常规C++枚举几乎具有相同的语法和能力:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // MyEnum.h
    #include <EnumTraits.h>
    #ifndef ENUM_INCLUDE_MULTI
    #pragma once
    #end if

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

    .cpp文件是3行样板文件:

    1
    2
    3
    4
    // MyEnum.cpp
    #define ENUM_DEFINE MyEnum
    #define ENUM_INCLUDE <MyEnum.h>
    #include <EnumTraits.inl>

    示例用法:

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

    代码

    此解决方案需要2个源文件:

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

    ……

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

    解释

    此实现利用了这样一个事实:枚举定义的元素的支撑列表也可以用作类成员初始化的支撑初始值设定项列表。

    当在EnumTraits.inl的上下文中对ETRAITS进行评估时,它扩展到EnumTraits<>类的静态成员定义。

    EDECL宏将每个枚举成员转换为初始值设定项列表值,然后将这些值传递给成员构造函数,以便填充枚举信息。

    EnumInitGuard类的设计目的是使用枚举初始值设定项值,然后折叠,留下一个纯粹的枚举数据列表。

    效益

    • 类语法
    • 适用于enumenum class(几乎相同)
    • 适用于具有任何数字基础类型的enum类型
    • 适用于具有自动、显式和分段初始值设定项值的enum类型
    • 用于批量重命名的作品(保留IntelliSense链接)
    • 只有5个预处理器符号(3个全局)

    enums不同,enum class类型中引用同一枚举中其他值的初始值设定项必须具有完全限定的值。

    不利益

    • 每个可查询的enum需要一个单独的.h/.cpp对。
    • 依赖于复杂的macroinclude魔法
    • 小语法错误会爆炸成更大的错误
    • 定义classnamespace范围的枚举是非常重要的。
    • 没有编译时初始化

    评论

    IntelliSense在打开EnumTraits.inl时会抱怨一些私有成员访问,但由于扩展的宏实际上是定义类成员,这实际上不是问题。

    头文件顶部的#ifndef ENUM_INCLUDE_MULTI块是一个小麻烦,可能会缩小为宏或其他内容,但它足够小,可以以当前大小使用。

    声明命名空间范围的枚举要求首先在其命名空间范围内向前声明枚举,然后在全局命名空间中定义枚举。此外,使用同一枚举值的任何枚举初始值设定项都必须完全限定这些值。

    1
    2
    3
    4
    5
    6
    7
    namespace ns { enum MyEnum : int; }
    enum ns::MyEnum : int ETRAITS
    {
        EDECL(AAA) = -8,
        EDECL(BBB) = '8',
        EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
    }

    我也很长一段时间对这个问题感到沮丧,还有一个问题就是如何将类型转换为字符串。然而,对于最后一个问题,我很惊讶于在标准C++中可以打印变量类型的解释吗?使用这个概念,我可以用CysExPR方式获得C++类型的名字吗?。使用此技术,可以构造一个类似的函数,以获取字符串形式的枚举值:

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    #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上进行了测试,但是应该能够通过在整型常量上稍加改动来适应其他编译器。当然,它仍然在引擎盖下使用宏,但至少有一个不需要访问枚举实现。


    编辑:在下面检查更新版本

    如上所述,N4113是这个问题的最终解决方案,但我们要等一年多才能看到它的出现。

    同时,如果你想要这样的特性,你需要借助"简单"的模板和一些预处理器的魔力。

    枚举器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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__);

    用法

    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
    #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::m_counter设置为0。(有人能指出标准中提到这种行为的地方吗?)预处理器魔术自动声明枚举器。

    缺点

    • 它不是一个真正的enum类型,因此不能提升到int。
    • 不能用于开关箱

    替代方案

    这一个牺牲了行编号(不是真的),但可以用于交换案例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #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和Clang上发生冲突。

    解决办法

    #line 1开始,从__LINE__中减去1。或者,不要使用-pedantic。当我们在做这件事的时候,不惜一切代价避免使用VC++,它一直是一个编译器的笑话。

    用法10现实生活的实施和使用

    R3Dvxel-EnUMR3D箱-液位

    快速引用

    #行号--cppreference.com


    以下解决方案基于给定枚举的std::array

    对于enumstd::string的转换,我们可以将枚举强制转换为size_t并从数组中查找字符串。操作是O(1),不需要分配堆。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #include <boost/preprocessor/seq/transform.hpp>
    #include <boost/preprocessor/seq/enum.hpp>
    #include <boost/preprocessor/stringize.hpp>

    #include <string>
    #include
    #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::stringenum的转换,我们必须对数组进行线性搜索,并将数组索引转换为enum

    在这里尝试使用示例:http://coliru.stacked-crooked.com/a/e4212f93bee65076

    编辑:改写了我的解决方案,以便自定义枚举可以在类中使用。


    这个GIST提供了一个基于C++可变模板的简单映射。

    这是基于GIST的基于类型的地图的C++ 17简化版本:

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

    示例用法:

    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
    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  // C++17 template
    struct _ {
        typedef decltype(K) key_t;
        typedef decltype(V) name_t;
        static constexpr key_t  key = K; // enum id value
        static constexpr name_t val = V; // enum id name
    };

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

    map可用于两个方向:

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

    这个例子可以在godbolt.org上找到。

    1
    2
    3
    4
    5
    6
    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的结果

    1
    2
    3
    main:
            mov     eax, 3
            ret


    我的答案在这里。

    使用此库(由我创建),您可以获取枚举常量的名称:https://github.com/neargye/name of

    1
    2
    3
    4
    5
    6
    7
    8
    // Name of enum
    auto c = Color::RED;
    NAMEOF_ENUM(c) ->"RED"
    // Name of enum
    nameof::nameof_enum(c) ->"RED"

    constexpr auto cx_name = NAMEOF_ENUM(c);
    static_assert("RED" == cx_name);

    此库使用编译器特定的黑客(基于__PRETTY_FUNCTION__/__FUNCSIG__),它在clang>=5、msvc>=15.3和gcc>=9上工作。


    有一个大约束的非常简单的解决方案:您不能将自定义值赋给enum值,但是使用正确的regex,您可以。您还可以添加一个映射来将它们转换回enum值,而无需付出更多的努力:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #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__)

    使用实例:

    1
    EnumToString(MyEnum, Red, Green, Blue);


    您可以使用反射库,例如思考:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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"


    在类/结构(具有公共成员的结构默认值)和重载运算符中使用枚举的解决方案:

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

    从外部看,它几乎完全类似于类枚举:

    1
    2
    3
    4
    5
    Color red;
    red = Color::RED;
    Color blue = Color::BLUE;

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

    这将输出"红色1 2"。您可能会重载<<以使蓝色输出成为一个字符串(尽管它可能会导致歧义,因此不可能),但它不会与color::green一起工作,因为它不会自动转换为颜色。

    隐式转换为枚举(隐式转换为int或给定类型)的目的是能够做到:

    1
    2
    Color color;
    switch (color) ...

    这是可行的,但也意味着这也是可行的:

    1
    int i = color;

    对于枚举类,它不会编译。如果使用枚举和整数重载两个函数,或者删除隐式转换,则应该小心。

    另一个解决方案将涉及使用实际的枚举类和静态成员:

    1
    2
    3
    4
    5
    6
    7
    struct Color
    {
        enum class Enum { RED, GREEN, BLUE };
        static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;

        //same as previous...
    };

    它可能占用更多的空间,并且制作时间更长,但对于隐式int转换会导致编译错误。我会用这个的!

    不过,这肯定会带来开销,但我认为它比我见过的其他代码更简单,看起来更好。还有添加功能的潜力,这些功能都可以在类中确定范围。

    编辑:这项工作和大多数可以在执行前编译:

    10


    我的解决方案没有宏用法。

    优势:

    • 你完全明白你在做什么
    • 访问是使用哈希映射的,因此对许多值枚举都很好
    • 无需考虑顺序或非连续值
    • 枚举到字符串和字符串到枚举转换,而添加的枚举值只能添加到一个附加位置

    缺点:

    • 您需要将所有枚举值复制为文本
    • 哈希映射中的访问必须考虑字符串大小写
    • 如果添加值很痛苦,则维护-必须同时添加枚举和直接转换映射

    所以…直到C++实现Cy.EnUn.PARSE功能的那一天,我将坚持这一点:

    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
    64
    65
    66
    67
                #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];

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    #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;
        }

    例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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 class"和助手类"。

    为了减少错误,所有的东西都只定义了一个枚举。

    这段代码的优点是自动创建的,用于反射和仔细查看宏代码,代码易于理解。"枚举到字符串"、"字符串到枚举"的性能都是算法O(1)。

    缺点是,当第一次使用时,将初始化枚举关联的字符串向量和映射的helper类。但如果你愿意的话,你也会被预先初始化。-


    我写了一个库来解决这个问题,除了得到消息外,所有的事情都发生在编译时。

    用途:

    使用宏DEF_MSG定义宏和消息对:

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

    CODE_OK是要使用的宏,"OK!"是对应的消息。

    使用get_message()或仅使用gm()获取消息:

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

    使用MSG_NUM找出定义了多少宏。这将自动增加,您无需执行任何操作。

    预定义消息:

    1
    2
    MSG_OK:     OK
    MSG_BOTTOM: Message bottom

    项目:libcodemsg

    库不会创建额外的数据。一切都发生在编译时。在message_def.h中,它生成一个名为MSG_CODEenum;在message_def.c中,它生成一个变量,保存static const char* _g_messages[]中的所有字符串。

    在这种情况下,库仅限于创建一个enum。这是返回值的理想选择,例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    MSG_CODE foo(void) {
        return MSG_OK; // or something else
    }

    MSG_CODE ret = foo();

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

    我喜欢这种设计的另一件事是,您可以在不同的文件中管理消息定义。

    我发现这个问题的解决方案看起来好多了。


    我的答案在这里。

    您可以同时获得枚举值名称和这些索引作为字符串的deque。

    这种方法只需要很少的复制、粘贴和编辑。

    当您需要枚举类类型值时,获得的结果需要从大小转换为枚举类类型,但我认为这是一种非常可移植和强大的处理枚举类的方法。

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


    (类似于https://stackoverflow.com/a/54967187/2338477,稍作修改)。

    这里是我自己的解决方案,具有最小的定义魔力和对单个枚举分配的支持。

    这是头文件:

    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
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    #pragma once
    #include <string>
    #include <map>
    #include <regex>

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

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

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

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

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

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

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

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

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


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





    /*
        Basic usage:

        Declare enumeration:

    DECLARE_ENUM( enumName,

        enumValue1,
        enumValue2,
        enumValue3 = 5,

        // comment
        enumValue4
    );

        Conversion logic:

        From enumeration to string:

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

        From string to enumeration:

           enumName value;

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


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

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

        return it->second;
    }

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

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

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

    下面是示例测试应用程序:

    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
    DECLARE_ENUM(TestEnum,
        ValueOne,
        ValueTwo,
        ValueThree = 5,
        ValueFour = 7
    );

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

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

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

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

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

    相同头文件的更新版本将保留在此处:

    https://github.com/tapika/cppscriptcore/blob/master/solutionprojectmodel/enumreflect.h


    嗯,还有另一个选择。典型的用例是,您需要为HTTP谓词使用常量,也需要使用其字符串版本值。

    例子:

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

    }

    动词类:

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

    public:

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

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

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

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

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

      // ---

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

    };

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

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


    简单的流过载怎么办?如果你不想做一些宏魔术,你仍然需要维护映射,但是我发现它比你原来的解决方案更干净。

    1
    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
    #include <cstdint>  // for std::uint_fast8_t
    #include
    #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 <<'
    '
    ;
    }