与其他类似的问题不同,这个问题是关于如何使用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++代码……
早在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的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可以在类中使用。
我的解决方案,使用预处理器定义。
您可以在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 + +。
如果你的枚举看起来像
enum MyEnum
{
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
你可以移动枚举的内容到一个新文件:
AAA = -8,
BBB = '8',
CCC = AAA + BBB
然后这些值可以被宏包围:
// default definition
#ifned ITEM(X,Y)
#define ITEM(X,Y)
#endif
// Items list
ITEM(AAA,-8)
ITEM(BBB,'8')
ITEM(CCC,AAA+BBB)
// clean up
#undef ITEM
下一步可能是重新包含枚举中的项:
enum MyEnum
{
#define ITEM(X,Y) X=Y,
#include "enum_definition_file"
};
最后你可以生成关于这个枚举的实用函数:
std::string ToString(MyEnum value)
{
switch( value )
{
#define ITEM(X,Y) case X: return #X;
#include "enum_definition_file"
}
return "";
}
MyEnum FromString(std::string const& value)
{
static std::map<std::string,MyEnum> converter
{
#define ITEM(X,Y) { #X, X },
#include "enum_definition_file"
};
auto it = converter.find(value);
if( it != converter.end() )
return it->second;
else
throw std::runtime_error("Value is missing");
}
该解决方案可以应用于旧的c++标准,它不使用现代的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););
}
我喜欢这种设计的另一个原因是,您可以在不同的文件中管理消息定义。
我发现这个问题的解看起来好多了。