与其他类似的问题不同,这个问题是关于如何使用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++代码……
我不太喜欢与此相关的所有花哨的框架(宏、模板和类),因为我认为使用它们会使代码更难理解,并且会增加编译时间并隐藏错误。总的来说,我想要一个简单的解决这个问题的方法。添加额外的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.)
我的意见,虽然这和行动要求的不完全相符。以下是有关参考资料。
namespace enums
{
template <typename T, T I, char ...Chars>
struct enums : std::integral_constant<T, I>
{
static constexpr char const chars[sizeof...(Chars)]{Chars...};
};
template <typename T, T X, typename S, std::size_t ...I>
constexpr auto make(std::index_sequence<I...>) noexcept
{
return enums<T, X, S().chars[I]...>();
}
#define ENUM(s, n) []() noexcept{\
struct S { char const (&chars)[sizeof(s)]{s}; };\
return enums::make<decltype(n), n, S>(\
std::make_index_sequence<sizeof(s)>());}()
#define ENUM_T(s, n)\
static constexpr auto s ## _tmp{ENUM(#s, n)};\
using s ## _enum_t = decltype(s ## _tmp)
template <typename T, typename ...A, std::size_t N>
inline auto map(char const (&s)[N]) noexcept
{
constexpr auto invalid(~T{});
auto r{invalid};
return
(
(
invalid == r ?
r = std::strncmp(A::chars, s, N) ? invalid : A{} :
r
),
...
);
}
}
int main()
{
ENUM_T(echo, 0);
ENUM_T(cat, 1);
ENUM_T(ls, 2);
std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;
std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;
return 0;
}
你生成了一个类型,你可以把它转换成整数或者字符串。
非常简单的解决方案,但有一个很大的限制:你不能将自定义值分配给枚举值,但通过正确的正则表达式,你可以这样做。你也可以添加一个映射,将它们转换回枚举值,而不需要更多的努力:
#include <vector>
#include <string>
#include <regex>
#include <iterator>
std::vector<std::string> split(const std::string& s,
const std::regex& delim = std::regex(",\\s*"))
{
using namespace std;
vector<string> cont;
copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1),
regex_token_iterator<string::const_iterator>(),
back_inserter(cont));
return cont;
}
#define EnumType(Type, ...) enum class Type { __VA_ARGS__ }
#define EnumStrings(Type, ...) static const std::vector<std::string> \
Type##Strings = split(#__VA_ARGS__);
#define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \
EnumStrings(Type, __VA_ARGS__)
使用的例子:
EnumToString(MyEnum, Red, Green, Blue);
在类/struct (struct默认为public成员)和重载操作符中使用enum的解决方案:
struct Color
{
enum Enum { RED, GREEN, BLUE };
Enum e;
Color() {}
Color(Enum e) : e(e) {}
Color operator=(Enum o) { e = o; return *this; }
Color operator=(Color o) { e = o.e; return *this; }
bool operator==(Enum o) { return e == o; }
bool operator==(Color o) { return e == o.e; }
operator Enum() const { return e; }
std::string toString() const
{
switch (e)
{
case Color::RED:
return "red";
case Color::GREEN:
return "green";
case Color::BLUE:
return "blue";
default:
return "unknown";
}
}
};
从外部看,它几乎完全像一个类枚举:
Color red;
red = Color::RED;
Color blue = Color::BLUE;
cout << red.toString() << " " << Color::GREEN << " " << blue << endl;
这将输出“red 12”。你可以重载<<使蓝色输出成为一个字符串(尽管这可能会导致歧义,所以不可能),但它不会与Color::GREEN一起工作,因为它不会自动转换为Color。
隐式转换为Enum(隐式转换为int或给定类型)的目的是能够做到:
Color color;
switch (color) ...
这是可行的,但这也意味着这也是可行的:
int i = color;
对于枚举类,它不会编译。
如果重载两个函数,接受枚举和整数,或者删除隐式转换…
另一个解决方案将涉及使用实际的枚举类和静态成员:
struct Color
{
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;
//same as previous...
};
它可能会占用更多的空间,并且花费更长的时间,但会导致隐式int转换的编译错误。我就会用这个!
虽然这样做肯定有开销,但我认为它比我见过的其他代码更简单,看起来更好。还可以添加功能,这些功能都可以在类中进行范围限定。
编辑:这是有效的,大多数可以在执行前编译:
class Color
{
public:
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;
constexpr Color() : e(Enum::RED) {}
constexpr Color(Enum e) : e(e) {}
constexpr bool operator==(Enum o) const { return e == o; }
constexpr bool operator==(Color o) const { return e == o.e; }
constexpr operator Enum() const { return e; }
Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; }
Color& operator=(Color o) { const_cast<Enum>(this->e) = o.e; return *this; }
std::string toString() const
{
switch (e)
{
case Enum::RED:
return "red";
case Enum::GREEN:
return "green";
case Enum::BLUE:
return "blue";
default:
return "unknown";
}
}
private:
const Enum e;
};
早在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在评论中发布了另一种方法。