与其他类似的问题不同,这个问题是关于如何使用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++代码……


当前回答

只要你愿意为每个可查询枚举编写单独的.h/.cpp对,这个解决方案的语法和功能与常规的c++枚举几乎相同:

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

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

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

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

使用示例:

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

Code

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

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

#define ETRAITS
#define EDECL(x) x

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

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

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

public:
    EnumTraits() = delete;

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

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

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

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

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

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

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

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

解释

此实现利用了这样一个事实,即枚举定义的带括号元素列表也可以用作类成员初始化的带括号初始化列表。

当ETRAITS在enumtrait .inl的上下文中计算时, 它展开为EnumTraits<>类的静态成员定义。

EDECL宏将每个枚举成员转换为初始化列表值,这些值随后被传递到成员构造函数中,以填充枚举信息。

EnumInitGuard类被设计为使用枚举初始化式值,然后折叠——留下一个纯枚举数据列表。

好处

c++式的语法 对枚举和枚举类的工作相同(*几乎) 适用于具有任何数字基础类型的enum类型 适用于具有自动、显式和分段初始化值的enum类型 大规模重命名工作(智能感知链接保留) 只有5个预处理器符号(3个全局的)

*与枚举相反,枚举类类型中引用同一枚举中的其他值的初始化式必须完全限定这些值

不利

每个可查询enum需要一个单独的.h/.cpp对 取决于错综复杂的宏和包括魔术 小的语法错误会演变成大得多的错误 定义类或命名空间作用域的枚举不是简单的 没有编译时初始化

评论

当打开EnumTraits时,智能感知会抱怨一些私有成员访问。Inl,但由于扩展的宏实际上是定义类成员,这实际上不是一个问题。

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

声明命名空间作用域的枚举要求首先在其命名空间作用域内向前声明枚举,然后在全局命名空间中定义枚举。此外,任何使用相同枚举值的枚举初始化器必须完全限定这些值。

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

其他回答

只要你愿意为每个可查询枚举编写单独的.h/.cpp对,这个解决方案的语法和功能与常规的c++枚举几乎相同:

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

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

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

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

使用示例:

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

Code

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

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

#define ETRAITS
#define EDECL(x) x

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

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

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

public:
    EnumTraits() = delete;

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

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

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

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI

#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

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

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

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

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

解释

此实现利用了这样一个事实,即枚举定义的带括号元素列表也可以用作类成员初始化的带括号初始化列表。

当ETRAITS在enumtrait .inl的上下文中计算时, 它展开为EnumTraits<>类的静态成员定义。

EDECL宏将每个枚举成员转换为初始化列表值,这些值随后被传递到成员构造函数中,以填充枚举信息。

EnumInitGuard类被设计为使用枚举初始化式值,然后折叠——留下一个纯枚举数据列表。

好处

c++式的语法 对枚举和枚举类的工作相同(*几乎) 适用于具有任何数字基础类型的enum类型 适用于具有自动、显式和分段初始化值的enum类型 大规模重命名工作(智能感知链接保留) 只有5个预处理器符号(3个全局的)

*与枚举相反,枚举类类型中引用同一枚举中的其他值的初始化式必须完全限定这些值

不利

每个可查询enum需要一个单独的.h/.cpp对 取决于错综复杂的宏和包括魔术 小的语法错误会演变成大得多的错误 定义类或命名空间作用域的枚举不是简单的 没有编译时初始化

评论

当打开EnumTraits时,智能感知会抱怨一些私有成员访问。Inl,但由于扩展的宏实际上是定义类成员,这实际上不是一个问题。

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

声明命名空间作用域的枚举要求首先在其命名空间作用域内向前声明枚举,然后在全局命名空间中定义枚举。此外,任何使用相同枚举值的枚举初始化器必须完全限定这些值。

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

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

早在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在评论中发布了另一种方法。

你可以使用一个反射库,比如Ponder:

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

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

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

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

我采用了@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); \
    }