与其他类似的问题不同,这个问题是关于如何使用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++代码……
下面的解决方案是基于给定enum的std::array<std::string,N>。
对于将enum转换为std::string,我们只需将enum转换为size_t,然后从数组中查找字符串。该操作是O(1),不需要堆分配。
#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <string>
#include <array>
#include <iostream>
#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)
// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X { \
enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
} \
static std::string to_string(Enum e) { \
auto a = array_of_strings(); \
return a[static_cast<size_t>(e)]; \
} \
}
对于std::string到enum的转换,我们必须对数组进行线性搜索,并将数组索引强制转换为enum。
这里有一些用法示例:http://coliru.stacked-crooked.com/a/e4212f93bee65076
编辑:重做我的解决方案,以便自定义Enum可以在类中使用。
我的解决方案是不使用宏。
优点:
你知道你在做什么
访问是通过哈希映射进行的,因此适用于许多有值枚举
不需要考虑顺序值或非连续值
既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];
这个要点提供了一个基于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
对于c++ 17 c++ 20,您将对反思研究小组(SG7)的工作感兴趣。还有一系列平行的论文,包括措辞(P0194)和基本原理、设计和进化(P0385)。(链接解析为每个系列的最新论文。)
从P0194r2(2016-10-15)开始,该语法将使用建议的reflexpr关键字:
meta::get_base_name_v<
meta::get_element_m<
meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>
例如(改编自Matus Choclik的reflexpr clang分支):
#include <reflexpr>
#include <iostream>
enum MyEnum { AAA = 1, BBB, CCC = 99 };
int main()
{
auto name_of_MyEnum_0 =
std::meta::get_base_name_v<
std::meta::get_element_m<
std::meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>;
// prints "AAA"
std::cout << name_of_MyEnum_0 << std::endl;
}
静态反射未能进入c++ 17(更确切地说,进入了2016年11月在Issaquah举行的标准会议上提出的可能是最终草案),但有信心它将进入c++ 20;摘自赫布·萨特的旅行报告:
特别是,反射研究小组审查了最新合并的静态反射提案,并发现它准备在我们的下一次会议上进入主要的进化小组,开始考虑TS或下一个标准的统一静态反射提案。
几天前我也遇到了同样的问题。没有一些奇怪的宏魔法,我找不到任何c++解决方案,所以我决定写一个CMake代码生成器来生成简单的开关case语句。
用法:
enum2str_generate(
PATH <path to place the files in>
CLASS_NAME <name of the class (also prefix for the files)>
FUNC_NAME <name of the (static) member function>
NAMESPACE <the class will be inside this namespace>
INCLUDES <LIST of files where the enums are defined>
ENUMS <LIST of enums to process>
BLACKLIST <LIST of constants to ignore>
USE_CONSTEXPR <whether to use constexpr or not (default: off)>
USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)>
)
该函数搜索文件系统中的include文件(使用include_directories命令提供的include目录),读取它们并执行一些regex来生成类和函数。
注意:constexpr在c++中意味着内联,所以使用USE_CONSTEXPR选项将只生成一个头类!
例子:
- includes a。h:
enum AAA : char { A1, A2 };
typedef enum {
VAL1 = 0,
VAL2 = 1,
VAL3 = 2,
VAL_FIRST = VAL1, // Ignored
VAL_LAST = VAL3, // Ignored
VAL_DUPLICATE = 1, // Ignored
VAL_STRANGE = VAL2 + 1 // Must be blacklisted
} BBB;
/ CMakeLists.txt:
include_directories( ${PROJECT_SOURCE_DIR}/includes ...)
enum2str_generate(
PATH "${PROJECT_SOURCE_DIR}"
CLASS_NAME "enum2Str"
NAMESPACE "abc"
FUNC_NAME "toStr"
INCLUDES "a.h" # WITHOUT directory
ENUMS "AAA" "BBB"
BLACKLIST "VAL_STRANGE")
生成:
/ enum2Str.hpp:
/*!
* \file enum2Str.hpp
* \warning This is an automatically generated file!
*/
#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP
#include <string>
#include <a.h>
namespace abc {
class enum2Str {
public:
static std::string toStr( AAA _var ) noexcept;
static std::string toStr( BBB _var ) noexcept;
};
}
#endif // ENUM2STR_HPP
/ enum2Str.cpp:
/*!
* \file enum2Str.cpp
* \warning This is an automatically generated file!
*/
#include "enum2Str.hpp"
namespace abc {
/*!
* \brief Converts the enum AAA to a std::string
* \param _var The enum value to convert
* \returns _var converted to a std::string
*/
std::string enum2Str::toStr( AAA _var ) noexcept {
switch ( _var ) {
case A1: return "A1";
case A2: return "A2";
default: return "<UNKNOWN>";
}
}
/*!
* \brief Converts the enum BBB to a std::string
* \param _var The enum value to convert
* \returns _var converted to a std::string
*/
std::string enum2Str::toStr( BBB _var ) noexcept {
switch ( _var ) {
case VAL1: return "VAL1";
case VAL2: return "VAL2";
case VAL3: return "VAL3";
default: return "<UNKNOWN>";
}
}
}
更新:
脚本现在还支持作用域枚举(枚举类|struct)和
我将它与一些我经常使用的其他脚本一起移动到一个单独的repo: https://github.com/mensinda/cmakeBuildTools
编辑:检查下面的新版本
如上所述,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
快速参考
这是一条直线