与其他类似的问题不同,这个问题是关于如何使用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++代码……
我的答案在这里。
你可以同时获得枚举值名称和这些索引,如deque of string。
这种方法只需要少量的复制粘贴和编辑。
当需要枚举类类型值时,需要将获得的结果从size_t类型转换为枚举类类型,但我认为这是一种非常可移植和强大的处理枚举类的方法。
enum class myenum
{
one = 0,
two,
three,
};
deque<string> ssplit(const string &_src, boost::regex &_re)
{
boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
boost::sregex_token_iterator e;
deque<string> tokens;
while (it != e)
tokens.push_back(*it++);
return std::move(tokens);
}
int main()
{
regex re(",");
deque<string> tokens = ssplit("one,two,three", re);
for (auto &t : tokens) cout << t << endl;
getchar();
return 0;
}
在类/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在评论中发布了另一种方法。
我不确定这种方法是否已经包含在其他答案中(实际上是,见下文)。我遇到过这个问题很多次,但没有找到不使用混淆宏或第三方库的解决方案。因此,我决定编写自己的模糊宏版本。
我想启用的是等价的
enum class test1 { ONE, TWO = 13, SIX };
std::string toString(const test1& e) { ... }
int main() {
test1 x;
std::cout << toString(x) << "\n";
std::cout << toString(test1::TWO) << "\n";
std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
//std::cout << toString(123);// invalid
}
应该打印
ONE
TWO
13
我不是宏的粉丝。然而,除非c++本身支持将枚举转换为字符串,否则必须使用某种代码生成和/或宏(我怀疑这种情况不会很快发生)。我正在使用x宏:
// 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。用法是这样的
#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"
现场演示
注意,我还没有包括选择基础类型。到目前为止,我还不需要它,但它应该是直接修改代码来启用它。
写完这篇文章后,我才意识到这和eferion的答案很相似。也许我以前读过,也许它是灵感的主要来源。我总是不能理解x -宏,直到我写了自己的;)。
#define ENUM_MAKE(TYPE, ...) \
enum class TYPE {__VA_ARGS__};\
struct Helper_ ## TYPE { \
static const String& toName(TYPE type) {\
int index = static_cast<int>(type);\
return splitStringVec()[index];}\
static const TYPE toType(const String& name){\
static std::unordered_map<String,TYPE> typeNameMap;\
if( typeNameMap.empty() )\
{\
const StringVector& ssVec = splitStringVec();\
for (size_t i = 0; i < ssVec.size(); ++i)\
typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\
}\
return typeNameMap[name];}\
static const StringVector& splitStringVec() {\
static StringVector typeNameVector;\
if(typeNameVector.empty()) \
{\
typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\
for (auto& name : typeNameVector)\
{\
name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \
name = String(#TYPE) + "::" + name;\
}\
}\
return typeNameVector;\
}\
};
using String = std::string;
using StringVector = std::vector<String>;
StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims)
{
StringVector ret;
// Pre-allocate some space for performance
ret.reserve(maxSplits ? maxSplits+1 : 10); // 10 is guessed capacity for most case
unsigned int numSplits = 0;
// Use STL methods
size_t start, pos;
start = 0;
do
{
pos = str.find_first_of(delims, start);
if (pos == start)
{
// Do nothing
start = pos + 1;
}
else if (pos == String::npos || (maxSplits && numSplits == maxSplits))
{
// Copy the rest of the string
ret.push_back( str.substr(start) );
break;
}
else
{
// Copy up to delimiter
ret.push_back( str.substr(start, pos - start) );
if(preserveDelims)
{
// Sometimes there could be more than one delimiter in a row.
// Loop until we don't find any more delims
size_t delimStart = pos, delimPos;
delimPos = str.find_first_not_of(delims, delimStart);
if (delimPos == String::npos)
{
// Copy the rest of the string
ret.push_back( str.substr(delimStart) );
}
else
{
ret.push_back( str.substr(delimStart, delimPos - delimStart) );
}
}
start = pos + 1;
}
// parse up to next real data
start = str.find_first_not_of(delims, start);
++numSplits;
} while (pos != String::npos);
return ret;
}
例子
ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3)
MY_TEST s1 = MY_TEST::MY_1;
MY_TEST s2 = MY_TEST::MY_2;
MY_TEST s3 = MY_TEST::MY_3;
String z1 = Helper_MY_TEST::toName(s1);
String z2 = Helper_MY_TEST::toName(s2);
String z3 = Helper_MY_TEST::toName(s3);
MY_TEST q1 = Helper_MY_TEST::toType(z1);
MY_TEST q2 = Helper_MY_TEST::toType(z2);
MY_TEST q3 = Helper_MY_TEST::toType(z3);
自动ENUM_MAKE宏生成“枚举类”和“枚举反射函数”辅助类。
为了减少错误,Everything只定义一个ENUM_MAKE。
这种代码的优点是自动创建用于反射和查看宏代码,易于理解的代码。'enum to string', 'string to enum'性能都是算法O(1)。
缺点是第一次使用时,枚举relection的string vector和map的helper类被初始化。
但是如果你想,你也会被预初始化。- - - - - -