与其他类似的问题不同,这个问题是关于如何使用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++代码……
这个要点提供了一个基于c++可变参数模板的简单映射。
这是一个c++ 17简化版的基于类型的映射的要点:
#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);
}
};
用法示例:
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<auto K, const char* (*V)()> // C++17 template<auto>
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 < keyvalue…>可以双向使用:
fasion_names:把(in fashion: emo)
fasion_names::把(“emo”)
这个例子可以在godbolt.org上找到
int main ()
{
constexpr auto str = fasion_names::get(fasion::emo);
constexpr auto fsn = fasion_names::get(str);
return (int) fsn;
}
结果:gc -7 -std=c++1z -Ofast -S
main:
mov eax, 3
ret
编辑:检查下面的新版本
如上所述,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); \
}
早在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到字符串和字符串到enum转换,而添加的enum值必须添加在一个额外的地方
缺点:
您需要将所有枚举值复制为文本
哈希映射中的访问必须考虑字符串大小写
维护如果添加值是痛苦的-必须添加在enum和直接翻译映射
所以…直到c++实现c# Enum。解析功能,我将坚持这个:
#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];