与其他类似的问题不同,这个问题是关于如何使用c++的新特性。

2008 c Is there a simple way to convert C++ enum to string? 2008 c Easy way to use variables of enum types as string in C? 2008 c++ How to easily map c++ enums to strings 2008 c++ Making something both a C identifier and a string? 2008 c++ Is there a simple script to convert C++ enum to string? 2009 c++ How to use enums as flags in C++? 2011 c++ How to convert an enum type variable to a string? 2011 c++ Enum to String C++ 2011 c++ How to convert an enum type variable to a string? 2012 c How to convert enum names to string in c 2013 c Stringifying an conditionally compiled enum in C

看了很多答案后,我还没有找到:

优雅的方式使用c++ 11、c++ 14或c++ 17的新特性 或者在Boost中使用一些现成的东西 还有一些东西计划在c++ 20中实现

例子

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

#include <map>
#include <iostream>

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

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

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

约束

请不要无价值的重复其他答案或基本链接。 请避免基于宏的臃肿答案,或尽量减少#define开销。 请不要手动enum ->字符串映射。

很高兴有

支持从不同于零的数字开始的enum值 支持负enum值 支持碎片enum值 支持类枚举(c++ 11) 支持类枚举:<类型>有任何允许的<类型> (c++ 11) 编译时(不是运行时)到字符串的转换, 或者至少在运行时快速执行(例如std::map不是一个好主意…) constexpr (c++ 11,然后在c++ 14/17/20中放松) noexcept (C + + 11) c++ 17/ c++ 20友好的代码片段

一个可能的想法是使用c++编译器功能,在编译时使用基于可变参数模板类和constexpr函数的元编程技巧来生成c++代码……


当前回答

早在2011年,我花了一个周末的时间对一个基于宏的解决方案进行微调,最终从未使用过它。

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

Vim宏比c++宏更有趣。

现实生活中的例子:

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

我将创建这个:

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

这就是我的生活方式。

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

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

其他回答

我不太喜欢与此相关的所有花哨的框架(宏、模板和类),因为我认为使用它们会使代码更难理解,并且会增加编译时间并隐藏错误。总的来说,我想要一个简单的解决这个问题的方法。添加额外的100行代码并不简单。

最初问题中给出的示例与我实际在生产中使用的代码非常接近。相反,我只想对原来的示例查找函数提出一些小的改进:

const std::string& magic(MyClass::MyEnum e)
{
    static const std::string OUT_OF_RANGE = "Out of range";
    #define ENTRY(v) { MyClass::MyEnum::v, "MyClass::MyEnum::" #v }
    static const std::unordered_map<MyClass::MyEnum, std::string> LOOKUP {
        ENTRY(AAA),
        ENTRY(BBB),
        ENTRY(CCC),
    };
    #undef ENTRY
    auto it  = LOOKUP.find(e);
    return ((it != LOOKUP.end()) ? it->second : OUT_OF_RANGE);
}

具体地说:

Internal data structures are now 'static' and 'const'. These are unchanging, so there is no need to construct these on every call to the function, and to do so would be very inefficient. Instead, these are constructed on the first call to the function only. Return value is now 'const std::string&'. This function will only return references to already-allocated std::string objects with 'static' lifetime, so there is no need to copy them when returning. Map type is now 'std::unordered_map' for O(1) access instead of std::map's O(log(N)) access. Use of the ENTRY macro allows somewhat more concise code and also avoids potential problems from typos made while entering names in the string literals. (If the programmer enters an invalid name, a compiler error will result.)

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

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

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

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

    return 0;
}

这种方法最糟糕的地方是维护起来很痛苦,但维护其他一些类似的方法也很痛苦,不是吗?

这种方法的优点:

使用可变温度(c++ 14特性) 使用模板专门化,我们可以在使用无效值时“检测”(但我不确定这是否有用)。 看起来很整洁。 名称查找在编译时完成。

生活的例子

Edit

你是对的;c++ 14变量模板方法不处理运行时情况,这是我的错,忘记了它:(

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

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

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

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

然后,变值模板诡计:

template <typename ENUM>
void initialize() {}

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

这里的“最佳技巧”是为map使用变量template,其中包含每个枚举条目的值和名称;这个映射在每个翻译单元中都是相同的,并且到处都有相同的名称,所以非常简单明了,如果我们像这样调用initialize函数:

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

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

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

但是可以通过SFINAE和重载<<运算符进行改进:

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

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

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

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

维护这一点也很麻烦,可以改进,但希望您能理解。

生活的例子

(better_enum库的方法)

在当前的c++中,有一种方法是这样做的:

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

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

用法:

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

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

// And so on...

所有操作都可以写成constexpr。你也可以实现@ecatmur回答中提到的c++ 17反射提议。

There is only one macro. I believe this is the minimum possible, because preprocessor stringization (#) is the only way to convert a token to a string in current C++. The macro is pretty unobtrusive – the constant declarations, including initializers, are pasted into a built-in enum declaration. This means they have the same syntax and meaning as in a built-in enum. Repetition is eliminated. The implementation is most natural and useful in at least C++11, due to constexpr. It can also be made to work with C++98 + __VA_ARGS__. It is definitely modern C++.


宏的定义有些复杂,所以我将从几个方面回答这个问题。

The bulk of this answer is an implementation that I think is suitable for the space constraints on StackOverflow. There is also a CodeProject article describing the basics of the implementation in a long-form tutorial. [Should I move it here? I think it's too much for a SO answer]. There is a full-featured library "Better Enums" that implements the macro in a single header file. It also implements N4428 Type Property Queries, the current revision of the C++17 reflection proposal N4113. So, at least for enums declared through this macro, you can have the proposed C++17 enum reflection now, in C++11/C++14.

将这个答案扩展到库的特性是很简单的——这里没有遗漏任何“重要”的东西。然而,这是相当乏味的,并且存在编译器可移植性问题。

免责声明:我是CodeProject文章和该库的作者。

您可以尝试这个答案中的代码、库以及在Wandbox中在线实现N4428。标准库文档还包含如何将其作为N4428使用的概述,其中解释了该建议的enumums部分。


解释

下面的代码实现了枚举和字符串之间的转换。然而,它也可以扩展到做其他事情,比如迭代。此答案将枚举包装在结构体中。你也可以在枚举旁边生成一个trait结构体。

策略是生成如下内容:

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

问题是:

We will end up with something like {Red = 1, Green, Blue} as the initializer for the values array. This is not valid C++, because Red is not an assignable expression. This is solved by casting each constant to a type T that has an assignment operator, but will drop the assignment: {(T)Red = 1, (T)Green, (T)Blue}. Similarly, we will end up with {"Red = 1", "Green", "Blue"} as the initializer for the names array. We will need to trim off the " = 1". I am not aware of a great way to do this at compile time, so we will defer this to run time. As a result, _to_string won't be constexpr, but _from_string can still be constexpr, because we can treat whitespace and equals signs as terminators when comparing with untrimmed strings. Both the above need a "mapping" macro that can apply another macro to each element in __VA_ARGS__. This is pretty standard. This answer includes a simple version that can handle up to 8 elements. If the macro is to be truly self-contained, it needs to declare no static data that requires a separate definition. In practice, this means arrays need special treatment. There are two possible solutions: constexpr (or just const) arrays at namespace scope, or regular arrays in non-constexpr static inline functions. The code in this answer is for C++11 and takes the former approach. The CodeProject article is for C++98 and takes the latter.


Code

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



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

#define CHOOSE_MAP_START(count) MAP ## count

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

#define IDENTITY(x) x

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

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

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



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

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

    U   _value;
};



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

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



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

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

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



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

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

and

// 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”在编译过程中被转换为枚举。

编辑:检查下面的新版本

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

同时,如果你想要这样的特性,你将需要求助于“简单的”模板和一些预处理器魔法。

枚举器

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

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

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

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

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

使用

#include <iostream>

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

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

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

简单的解释

Enum<T>::m_counter在每个命名空间声明中设置为0。 (有人能告诉我^^这种行为^^在标准中被提到了吗?) 预处理器的魔力使枚举数的声明自动化。

缺点

它不是真正的枚举类型,因此不能提升为int 不能在交换机情况下使用


可选择的解决方案

这种方法牺牲了线路编号(不是真的),但可以在开关情况下使用。

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

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

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

勘误表

在GCC和clang上,# 0行与-迂腐冲突。

解决方案

要么从#第1行开始,然后从__LINE__减去1。 或者,不要用-pedantic。 当我们谈到它的时候,要不惜一切代价避免vc++,它一直是编译器的一个笑话。

使用

#include <iostream>

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

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

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

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

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

    //...
    }

    return 0;
}

真实的实现和使用

r3d体素- Enum r3dVoxel - ELoggingLevel

快速参考

这是一条直线

我采用了@antron的想法,并以不同的方式实现:生成一个真正的枚举类。

这个实现满足了最初问题中列出的所有要求,但目前只有一个真正的限制:它假设枚举值要么没有提供,要么如果提供了,必须从0开始,并且无间隙地按顺序递增。

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

解决方案使用一些c++17作为内联变量,但如果需要,这可以很容易地避免。因为简单,它还使用boost:trim。

最重要的是,它只需要30行代码,而且没有黑魔法宏。 代码如下。它的意思是放在头和包括在多个编译模块。

它可以使用与本文前面建议的相同的方式:

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

请让我知道这是有用的,以及如何进一步改进。


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