与其他类似的问题不同,这个问题是关于如何使用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++代码……
我的解决方案,使用预处理器定义。
您可以在https://repl.it/@JomaCorpFX/nameof#main.cpp上查看此代码
#include <iostream>
#include <stdexcept>
#include <regex>
typedef std::string String;
using namespace std::literals::string_literals;
class Strings
{
public:
static String TrimStart(const std::string& data)
{
String s = data;
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
return s;
}
static String TrimEnd(const std::string& data)
{
String s = data;
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(),
s.end());
return s;
}
static String Trim(const std::string& data)
{
return TrimEnd(TrimStart(data));
}
static String Replace(const String& data, const String& toFind, const String& toReplace)
{
String result = data;
size_t pos = 0;
while ((pos = result.find(toFind, pos)) != String::npos)
{
result.replace(pos, toFind.length(), toReplace);
pos += toReplace.length();
pos = result.find(toFind, pos);
}
return result;
}
};
static String Nameof(const String& name)
{
std::smatch groups;
String str = Strings::Trim(name);
if (std::regex_match(str, groups, std::regex(u8R"(^&?([_a-zA-Z]\w*(->|\.|::))*([_a-zA-Z]\w*)$)")))
{
if (groups.size() == 4)
{
return groups[3];
}
}
throw std::invalid_argument(Strings::Replace(u8R"(nameof(#). Invalid identifier "#".)", u8"#", name));
}
#define nameof(name) Nameof(u8## #name ## s)
#define cnameof(name) Nameof(u8## #name ## s).c_str()
enum TokenType {
COMMA,
PERIOD,
Q_MARK
};
struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};
int main() {
String greetings = u8"Hello"s;
std::cout << nameof(COMMA) << std::endl;
std::cout << nameof(TokenType::PERIOD) << std::endl;
std::cout << nameof(TokenType::Q_MARK) << std::endl;
std::cout << nameof(int) << std::endl;
std::cout << nameof(std::string) << std::endl;
std::cout << nameof(Strings) << std::endl;
std::cout << nameof(String) << std::endl;
std::cout << nameof(greetings) << std::endl;
std::cout << nameof(&greetings) << std::endl;
std::cout << nameof(greetings.c_str) << std::endl;
std::cout << nameof(std::string::npos) << std::endl;
std::cout << nameof(MyClass::MyEnum::AAA) << std::endl;
std::cout << nameof(MyClass::MyEnum::BBB) << std::endl;
std::cout << nameof(MyClass::MyEnum::CCC) << std::endl;
std::cin.get();
return 0;
}
输出
COMMA
PERIOD
Q_MARK
int
string
Strings
String
greetings
greetings
c_str
npos
AAA
BBB
CCC
铛
视觉C + +。
我写了一个库来解决这个问题,所有的事情都发生在编译时,除了获取消息。
用法:
使用宏DEF_MSG定义宏和消息对:
DEF_MSG(CODE_OK, "OK!")
DEF_MSG(CODE_FAIL, "Fail!")
CODE_OK是要使用的宏,“OK!”是相应的消息。
使用get_message()或gm()来获取消息:
get_message(CODE_FAIL); // will return "Fail!"
gm(CODE_FAIL); // works exactly the same as above
使用MSG_NUM查找已经定义了多少个宏。它会自动增加,你不需要做任何事情。
预定义的消息:
MSG_OK: OK
MSG_BOTTOM: Message bottom
项目:libcodemsg
标准库不会创建额外的数据。一切都发生在编译时。在message_def.h中,它生成一个名为MSG_CODE的enum;在message_def.c中,它生成一个变量,保存静态const char* _g_messages[]中的所有字符串。
在这种情况下,库只能创建一个枚举。这对于返回值非常理想,例如:
MSG_CODE foo(void) {
return MSG_OK; // or something else
}
MSG_CODE ret = foo();
if (MSG_OK != ret) {
printf("%s\n", gm(ret););
}
我喜欢这种设计的另一个原因是,您可以在不同的文件中管理消息定义。
我发现这个问题的解看起来好多了。
我采用了@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); \
}
编辑:检查下面的新版本
如上所述,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
快速参考
这是一条直线
我的答案在这里。
你可以同时获得枚举值名称和这些索引,如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;
}