Есть ли простой способ конвертировать перечисление C++ в строку?
Предположим, у нас есть некоторые именованные перечисления:
enum MyEnum {
FOO,
BAR = 0x50
};
То, что я нашел, - это скрипт (любой язык), который сканирует все заголовки в моем проекте и генерирует заголовок с одной функцией на перечисление.
char* enum_to_string(MyEnum t);
И реализация с чем-то вроде этого:
char* enum_to_string(MyEnum t){
switch(t){
case FOO:
return "FOO";
case BAR:
return "BAR";
default:
return "INVALID ENUM";
}
}
Gotcha действительно с перечислениями typedefed и безымянными перечислениями стиля C. Кто-нибудь знает что-нибудь для этого?
РЕДАКТИРОВАТЬ: Решение не должно изменять мой источник, за исключением сгенерированных функций. Перечисления в API, поэтому использование решений, предложенных до сих пор, просто не вариант.
36 ответов
Вы можете проверить GCCXML.
Запуск GCCXML для вашего примера кода дает:
<GCC_XML>
<Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
<Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
<Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
<EnumValue name="FOO" init="0"/>
<EnumValue name="BAR" init="80"/>
</Enumeration>
<File id="f0" name="my_enum.h"/>
</GCC_XML>
Вы можете использовать любой язык, который вы предпочитаете, чтобы извлечь теги Enumeration и EnumValue и сгенерировать нужный код.
X-макросы - лучшее решение. Пример:
#include <iostream>
enum Colours {
# define X(a) a,
# include "colours.def"
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
# include "colours.def"
# undef X
0
};
std::ostream& operator<<(std::ostream& os, enum Colours c)
{
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c];
}
int main()
{
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}
colours.def:
X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
Однако я обычно предпочитаю следующий метод, чтобы можно было немного подправить строку.
#define X(a, b) a,
#define X(a, b) b,
X(Red, "red")
X(Green, "green")
// etc.
@hydroo: без дополнительного файла:
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
SOME_ENUM(MAKE_ENUM)
};
#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
SOME_ENUM(MAKE_STRINGS)
};
Я обычно создаю массив C с именами в том же порядке и положении, что и значения enum.
например.
enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };
тогда вы можете использовать массив в местах, где вы хотите, чтобы человек читал значение, например
colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];
Вы можете немного поэкспериментировать с оператором stringizing (см. # В справочнике препроцессора), который будет делать то, что вы хотите, в некоторых случаях, например:
#define printword(XX) cout << #XX;
printword(red);
напечатает "красный" на стандартный вывод. К сожалению, это не будет работать для переменной (так как вы получите имя переменной распечатано)
У меня есть невероятно простой в использовании макрос, который делает это абсолютно СУХОЙ модой. Он включает в себя различные макросы и простую магию синтаксического анализа. Вот оно:
#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
else if(str[i] == ',') { \
strings.push_back(temp.str()); \
temp.str(std::string());\
} \
else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;}
Чтобы использовать это в своем коде, просто выполните:
AWESOME_MAKE_ENUM(Animal,
DOG,
CAT,
HORSE
);
Это можно сделать в C++11
#include <map>
enum MyEnum { AA, BB, CC, DD };
static std::map< MyEnum, const char * > info = {
{AA, "This is an apple"},
{BB, "This is a book"},
{CC, "This is a coffee"},
{DD, "This is a door"}
};
void main()
{
std::cout << info[AA] << endl
<< info[BB] << endl
<< info[CC] << endl
<< info[DD] << endl;
}
Я только что изобрел это колесо сегодня и решил поделиться им.
Эта реализация не требует каких-либо изменений в коде, который определяет константы, которые могут быть перечислениями или #define
s или что-то еще, что превращается в целое число - в моем случае у меня были символы, определенные в терминах других символов. Это также хорошо работает с разреженными значениями. Он даже позволяет использовать несколько имен для одного и того же значения, всегда возвращая первое. Единственным недостатком является то, что вам необходимо составить таблицу констант, которые могут устареть, например, при добавлении новых.
struct IdAndName
{
int id;
const char * name;
bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }
const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
std::stable_sort(table_begin, table_end);
IdAndName searchee = { id, NULL };
IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
return (p == table_end || p->id != id) ? NULL : p->name;
}
template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
return IdToName(id, &table[0], &table[N]);
}
Пример того, как вы будете использовать это:
static IdAndName WindowsErrorTable[] =
{
ID_AND_NAME(INT_MAX), // flag value to indicate unsorted table
ID_AND_NAME(NO_ERROR),
ID_AND_NAME(ERROR_INVALID_FUNCTION),
ID_AND_NAME(ERROR_FILE_NOT_FOUND),
ID_AND_NAME(ERROR_PATH_NOT_FOUND),
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
ID_AND_NAME(ERROR_ACCESS_DENIED),
ID_AND_NAME(ERROR_INVALID_HANDLE),
ID_AND_NAME(ERROR_ARENA_TRASHED),
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
ID_AND_NAME(ERROR_INVALID_BLOCK),
ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
ID_AND_NAME(ERROR_BAD_FORMAT),
ID_AND_NAME(ERROR_INVALID_ACCESS),
ID_AND_NAME(ERROR_INVALID_DATA),
ID_AND_NAME(ERROR_INVALID_DRIVE),
ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
ID_AND_NAME(ERROR_NO_MORE_FILES)
};
const char * error_name = IdToName(GetLastError(), WindowsErrorTable);
IdToName
функция опирается на std::lower_bound
сделать быстрый поиск, который требует сортировки таблицы. Если первые две записи в таблице не в порядке, функция сортирует их автоматически.
Изменить: комментарий заставил меня задуматься о другом способе использования того же принципа. Макрос упрощает генерацию большого switch
заявление.
#define ID_AND_NAME(x) case x: return #x
const char * WindowsErrorToName(int id)
{
switch(id)
{
ID_AND_NAME(ERROR_INVALID_FUNCTION);
ID_AND_NAME(ERROR_FILE_NOT_FOUND);
ID_AND_NAME(ERROR_PATH_NOT_FOUND);
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
ID_AND_NAME(ERROR_ACCESS_DENIED);
ID_AND_NAME(ERROR_INVALID_HANDLE);
ID_AND_NAME(ERROR_ARENA_TRASHED);
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
ID_AND_NAME(ERROR_INVALID_BLOCK);
ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
ID_AND_NAME(ERROR_BAD_FORMAT);
ID_AND_NAME(ERROR_INVALID_ACCESS);
ID_AND_NAME(ERROR_INVALID_DATA);
ID_AND_NAME(ERROR_INVALID_DRIVE);
ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
ID_AND_NAME(ERROR_NO_MORE_FILES);
default: return NULL;
}
}
QT может получить это из (благодаря компилятору мета-объекта): ссылка
Немного поздно для вечеринки здесь, но мне очень нравится этот шаблон, потому что он спасает вас от ошибок копирования и пасты и не будет компилироваться, если перечисление не будет сопоставлено со строкой. Он также имеет то преимущество, что он оченьconstexpr
дружелюбный, поэтому он очень хорошо встраивается. Он также не требует промежуточных классов, операторов switch или значений времени выполнения.
// Create a mapping between the enum value and the string
#define MY_ENUM_LIST(DECLARE) \
DECLARE(foo, "This is a foo!") \
DECLARE(bar, "This is a bar!") \
DECLARE(bam, "This is a bam!")
// Define the enum officially
enum class MyEnum {
#define ENUM_ENTRY(NAME, TEXT) NAME, // TEXT expressly not used here
MY_ENUM_LIST(ENUM_ENTRY)
#undef ENUM_ENTRY // Always undef as a good citizen ;)
};
// Create a template function that would fail to compile if called
template <MyEnum KEY> constexpr const char* MyEnumText() {}
// Specialize that bad function with versions that map the enum value to the string declared above
#define ENUM_FUNC(NAME, TEXT) template <> constexpr const char* MyEnumText<MyEnum::NAME>() { return TEXT; }
MY_ENUM_LIST(ENUM_FUNC)
#undef ENUM_FUNC
То, как вы его используете, довольно прямолинейно. Если вы всегда жестко кодируете значение enum на сайте, где вам нужна строка, вы просто вызываете специализированную версиюMyEnumText
:
const auto text{::MyEnumText<MyEnum::foo>()}; // inlines beautifully
Если вам нужно обрабатывать динамические значения перечисления, вы можете добавить этот дополнительный помощник:
constexpr const char* MyEnumText(MyEnum key) {
switch (key) {
#define ENUM_CASE(NAME, TEXT) case MyEnum::NAME: return MyEnumText<MyEnum::NAME>();
MY_ENUM_LIST(ENUM_CASE)
#undef ENUM_CASE
}
return nullptr;
}
Который вызывается аналогично специализации шаблона:
const auto text{::MyEnumText(MyEnum::foo)}; // inlines beautifully
или
const MyEnum e{GetTheEnumValue()};
const auto text{::MyEnumText(e)};
#define stringify( name ) # name
enum MyEnum {
ENUMVAL1
};
...stuff...
stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1
Здесь однофайловое решение (на основе элегантного ответа @Marcin:
#include <iostream>
#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \
enum Colours {
# define X(a) a,
ENUM_TXT
# undef X
ColoursCount
};
char const* const colours_str[] = {
# define X(a) #a,
ENUM_TXT
# undef X
0
};
std::ostream& operator<<(std::ostream& os, enum Colours c)
{
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c] << std::endl;
}
int main()
{
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}
Интересно посмотреть количество способов. вот тот, который я использовал давным-давно:
в файле myenummap.h:
#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
mymap()
{
this->operator[]( one ) = "ONE";
this->operator[]( two ) = "TWO";
this->operator[]( three ) = "THREE";
this->operator[]( five ) = "FIVE";
this->operator[]( six ) = "SIX";
this->operator[]( seven ) = "SEVEN";
};
~mymap(){};
};
в main.cpp
#include "myenummap.h"
...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;
Это не const, но это удобно.
Вот еще один способ, который использует функции C++11. Это const, не наследует контейнер STL и немного аккуратнее:
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
typedef std::pair<int,std::string> mapping;
auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);};
std::vector<mapping> const nummap =
{
m(one,"one"),
m(two,"two"),
m(three,"three"),
m(five,"five"),
m(six,"six"),
m(seven,"seven"),
};
for(auto i : nummap)
{
if(i.first==static_cast<int>(e))
{
return i.second;
}
}
return "";
}
int main()
{
// std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
return 0;
}
#include <stdarg.h>
#include <algorithm>
#include <string>
#include <vector>
#include <sstream>
#include <map>
#define SMART_ENUM(EnumName, ...) \
class EnumName \
{ \
private: \
static std::map<int, std::string> nameMap; \
public: \
enum {__VA_ARGS__}; \
private: \
static std::map<int, std::string> initMap() \
{ \
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.push_back(buf_1); \
map<int, string> tmp; \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
{ \
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
} \
return tmp; \
} \
public: \
static std::string toString(int aInt) \
{ \
return nameMap[aInt]; \
} \
}; \
std::map<int, std::string> \
EnumName::nameMap = EnumName::initMap();
Использование:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);
Макро решение Suma это хорошо. Вам не нужно иметь два разных макроса, хотя. C++ с радостью включит заголовок дважды. Просто не включайте охрану.
Так что у вас будет foobar.h, определяющий просто
ENUM(Foo, 1)
ENUM(Bar, 2)
и вы бы включили это так:
#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"
enumfactory.h сделает 2 #include ENUMFACTORY_ARGUMENT
s. В первом раунде он расширяет ENUM, как Сума DECLARE_ENUM
; во втором туре ENUM работает как DEFINE_ENUM
,
Вы также можете включать enumfactory.h несколько раз, если вы переходите в разные #define для ENUMFACTORY_ARGUMENT
Добавляя еще больше простоты использования к фантастическому ответу Джаспера Беккерса:
Настроить один раз:
#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
enum enumName { \
source(MAKE_ENUM) \
};\
const char* const enumStringName[] = { \
source(MAKE_STRINGS) \
};
Тогда для использования:
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)
Это модификация ответа @user3360260. Имеет следующие новые функции
MyEnum fromString(const string&)
служба поддержки- компилируется с VisualStudio 2012
- enum - это фактический тип POD (не только объявления const), поэтому вы можете присвоить его переменной.
- добавлена C++ функция "range" (в виде вектора), чтобы разрешить итерацию "foreach" над enum
Использование:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo); // static method
cout << foo.toString(); // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");
// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
cout << x.toString() << endl;
}
Вот код
#define SMART_ENUM(EnumName, ...) \
class EnumName \
{ \
public: \
EnumName() : value(0) {} \
EnumName(int x) : value(x) {} \
public: \
enum {__VA_ARGS__}; \
private: \
static void initMap(std::map<int, std::string>& tmp) \
{ \
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.push_back(buf_1); \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
{ \
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
} \
} \
int value; \
public: \
operator int () const { return value; } \
std::string toString(void) const { \
return toString(value); \
} \
static std::string toString(int aInt) \
{ \
return nameMap()[aInt]; \
} \
static EnumName fromString(const std::string& s) \
{ \
auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
return p.second == s; \
}); \
if (it == nameMap().end()) { \
/*value not found*/ \
throw EnumName::Exception(); \
} else { \
return EnumName(it->first); \
} \
} \
class Exception : public std::exception {}; \
static std::map<int,std::string>& nameMap() { \
static std::map<int,std::string> nameMap0; \
if (nameMap0.size() ==0) initMap(nameMap0); \
return nameMap0; \
} \
static std::vector<EnumName> allValues() { \
std::vector<EnumName> x{ __VA_ARGS__ }; \
return x; \
} \
bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};
Обратите внимание, что преобразование toString - быстрый поиск, в то время как преобразование из String - медленный линейный поиск. Но в любом случае строки так дороги (и связанный с ними файловый ввод-вывод), я не чувствовал необходимости оптимизировать или использовать bimap.
Другой ответ: в некоторых контекстах имеет смысл определить ваше перечисление в не кодовом формате, таком как файл CSV, YAML или XML, а затем сгенерировать как код перечисления C++, так и строковый код из определения. Этот подход может или не может быть практичным в вашем приложении, но об этом нужно помнить.
Обратите внимание, что ваша функция преобразования должна в идеале возвращать const char *.
Если вы можете позволить себе перечислять перечисления в их отдельных заголовочных файлах, вы можете сделать что-то подобное с макросами (о, это будет ужасно):
#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"
Где enum_def.h имеет:
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };
И enum_conv.h имеет:
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }
И, наконец, colour.h имеет:
ENUM_START(colour)
ENUM_ADD(red, 0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue, 0x0000ff)
ENUM_END
И вы можете использовать функцию преобразования как:
printf("%s", colour_to_string(colour::red));
Это ужасно, но это единственный способ (на уровне препроцессора), который позволяет вам определять ваше перечисление только в одном месте в вашем коде. Поэтому ваш код не подвержен ошибкам из-за изменений в перечислении. Ваше определение enum и функция преобразования всегда будут синхронизированы. Впрочем, повторюсь, это некрасиво:)
Я хочу опубликовать это на тот случай, если кто-то посчитает это полезным.
В моем случае мне просто нужно сгенерировать ToString()
а также FromString()
функции для одного перечисления C++11 из одного .hpp
файл.
Я написал скрипт на python, который анализирует заголовочный файл, содержащий элементы enum, и генерирует функции в новом .cpp
файл.
Вы можете добавить этот сценарий в CMakeLists.txt с помощью execute_process или как событие перед сборкой в Visual Studio. .cpp
файл будет сгенерирован автоматически, без необходимости вручную обновлять его каждый раз, когда добавляется новый элемент enum.
generate_enum_strings.py
# This script is used to generate strings from C++ enums
import re
import sys
import os
fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])
with open(fileName, 'r') as f:
content = f.read().replace('\n', '')
searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)
textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += ' std::string ToString(ErrorCode errorCode)\n'
textOut += ' {\n'
textOut += ' switch (errorCode)\n'
textOut += ' {\n'
for token in tokens:
textOut += ' case ' + enumName + '::' + token + ':\n'
textOut += ' return "' + token + '";\n'
textOut += ' default:\n'
textOut += ' return "Last";\n'
textOut += ' }\n'
textOut += ' }\n'
textOut += '\n'
textOut += ' ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += ' {\n'
textOut += ' if ("' + tokens[0] + '" == errorCode)\n'
textOut += ' {\n'
textOut += ' return ' + enumName + '::' + tokens[0] + ';\n'
textOut += ' }\n'
for token in tokens[1:]:
textOut += ' else if("' + token + '" == errorCode)\n'
textOut += ' {\n'
textOut += ' return ' + enumName + '::' + token + ';\n'
textOut += ' }\n'
textOut += '\n'
textOut += ' return ' + enumName + '::Last;\n'
textOut += ' }\n'
textOut += '}\n'
fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)
Пример:
ErrorCode.hpp
#pragma once
#include <string>
#include <cstdint>
namespace myns
{
enum class ErrorCode : uint32_t
{
OK = 0,
OutOfSpace,
ConnectionFailure,
InvalidJson,
DatabaseFailure,
HttpError,
FileSystemError,
FailedToEncrypt,
FailedToDecrypt,
EndOfFile,
FailedToOpenFileForRead,
FailedToOpenFileForWrite,
FailedToLaunchProcess,
Last
};
std::string ToString(ErrorCode errorCode);
ErrorCode FromString(const std::string &errorCode);
}
Бежать python generate_enum_strings.py ErrorCode.hpp
Результат:
ErrorCode.cpp
#include "ErrorCode.hpp"
namespace myns
{
std::string ToString(ErrorCode errorCode)
{
switch (errorCode)
{
case ErrorCode::OK:
return "OK";
case ErrorCode::OutOfSpace:
return "OutOfSpace";
case ErrorCode::ConnectionFailure:
return "ConnectionFailure";
case ErrorCode::InvalidJson:
return "InvalidJson";
case ErrorCode::DatabaseFailure:
return "DatabaseFailure";
case ErrorCode::HttpError:
return "HttpError";
case ErrorCode::FileSystemError:
return "FileSystemError";
case ErrorCode::FailedToEncrypt:
return "FailedToEncrypt";
case ErrorCode::FailedToDecrypt:
return "FailedToDecrypt";
case ErrorCode::EndOfFile:
return "EndOfFile";
case ErrorCode::FailedToOpenFileForRead:
return "FailedToOpenFileForRead";
case ErrorCode::FailedToOpenFileForWrite:
return "FailedToOpenFileForWrite";
case ErrorCode::FailedToLaunchProcess:
return "FailedToLaunchProcess";
case ErrorCode::Last:
return "Last";
default:
return "Last";
}
}
ErrorCode FromString(const std::string &errorCode)
{
if ("OK" == errorCode)
{
return ErrorCode::OK;
}
else if("OutOfSpace" == errorCode)
{
return ErrorCode::OutOfSpace;
}
else if("ConnectionFailure" == errorCode)
{
return ErrorCode::ConnectionFailure;
}
else if("InvalidJson" == errorCode)
{
return ErrorCode::InvalidJson;
}
else if("DatabaseFailure" == errorCode)
{
return ErrorCode::DatabaseFailure;
}
else if("HttpError" == errorCode)
{
return ErrorCode::HttpError;
}
else if("FileSystemError" == errorCode)
{
return ErrorCode::FileSystemError;
}
else if("FailedToEncrypt" == errorCode)
{
return ErrorCode::FailedToEncrypt;
}
else if("FailedToDecrypt" == errorCode)
{
return ErrorCode::FailedToDecrypt;
}
else if("EndOfFile" == errorCode)
{
return ErrorCode::EndOfFile;
}
else if("FailedToOpenFileForRead" == errorCode)
{
return ErrorCode::FailedToOpenFileForRead;
}
else if("FailedToOpenFileForWrite" == errorCode)
{
return ErrorCode::FailedToOpenFileForWrite;
}
else if("FailedToLaunchProcess" == errorCode)
{
return ErrorCode::FailedToLaunchProcess;
}
else if("Last" == errorCode)
{
return ErrorCode::Last;
}
return ErrorCode::Last;
}
}
Это было мое решение с BOOST:
#include <boost/preprocessor.hpp>
#define X_STR_ENUM_TOSTRING_CASE(r, data, elem) \
case elem : return BOOST_PP_STRINGIZE(elem);
#define X_ENUM_STR_TOENUM_IF(r, data, elem) \
else if(data == BOOST_PP_STRINGIZE(elem)) return elem;
#define STR_ENUM(name, enumerators) \
enum name { \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
\
inline const QString enumToStr(name v) \
{ \
switch (v) \
{ \
BOOST_PP_SEQ_FOR_EACH( \
X_STR_ENUM_TOSTRING_CASE, \
name, \
enumerators \
) \
\
default: \
return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \
} \
} \
\
template <typename T> \
inline const T strToEnum(QString v); \
\
template <> \
inline const name strToEnum(QString v) \
{ \
if(v=="") \
throw std::runtime_error("Empty enum value"); \
\
BOOST_PP_SEQ_FOR_EACH( \
X_ENUM_STR_TOENUM_IF, \
v, \
enumerators \
) \
\
else \
throw std::runtime_error( \
QString("[Unknown value %1 for enum %2]") \
.arg(v) \
.arg(BOOST_PP_STRINGIZE(name)) \
.toStdString().c_str()); \
}
Чтобы создать enum, объявите:
STR_ENUM
(
SERVICE_RELOAD,
(reload_log)
(reload_settings)
(reload_qxml_server)
)
Для конверсий:
SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);
Вы можете использовать библиотеку отражений, например, Ponder. Вы регистрируете перечисления, а затем можете конвертировать их назад и вперед с помощью API.
enum class MyEnum
{
Zero = 0,
One = 1,
Two = 2
};
ponder::Enum::declare<MyEnum>()
.value("Zero", MyEnum::Zero)
.value("One", MyEnum::One)
.value("Two", MyEnum::Two);
ponder::EnumObject zero(MyEnum::Zero);
zero.name(); // -> "Zero"
Я делаю это с отдельными параллельными классами-обертками enum, которые генерируются с помощью макросов. Есть несколько преимуществ:
- Может генерировать их для перечислений, которые я не определяю (например: перечисления заголовков платформы ОС)
- Может включать проверку диапазона в класс обертки
- Может сделать "умнее" форматирование с помощью перечисления битовых полей
Недостатком, конечно, является то, что мне нужно дублировать значения перечисления в классах форматирования, и у меня нет сценария для их генерации. Кроме этого, похоже, это работает довольно хорошо.
Вот пример перечисления из моей кодовой базы, без кода всей инфраструктуры, который реализует макросы и шаблоны, но вы можете понять это:
enum EHelpLocation
{
HELP_LOCATION_UNKNOWN = 0,
HELP_LOCAL_FILE = 1,
HELP_HTML_ONLINE = 2,
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
static inline CString FormatEnum( EHelpLocation eValue )
{
switch ( eValue )
{
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
default:
return FormatAsNumber( eValue );
}
}
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;
Идея заключается в том, что вместо использования EHelpLocation вы используете SEHelpLocation; все работает одинаково, но вы получаете проверку диапазона и метод Format() для самой переменной enum. Если вам нужно отформатировать отдельное значение, вы можете использовать CEnumFormatter_EHelpLocation::FormatEnum(...).
Надеюсь, это полезно. Я понимаю, что это также не решает первоначальный вопрос о сценарии, который фактически генерирует другой класс, но я надеюсь, что структура поможет кому-то попытаться решить ту же проблему или написать такой сценарий.
Это неизданное программное обеспечение, но, похоже, BOOST_ENUM от Фрэнка Лауба вполне может подойти. Что мне нравится в этом, так это то, что вы можете определить перечисление в пределах класса, которое обычно не позволяет делать большинство перечислений на основе макросов. Он находится в Boost Vault по адресу: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=&; Он не видел каких-либо разработок с 2006 года, поэтому я не знать, насколько хорошо он компилируется с новыми выпусками Boost. Посмотрите в libs/test пример использования.
Проблема с ответом 0 состоит в том, что двоичные значения перечисления не обязательно начинаются с 0 и не обязательно являются смежными.
Когда мне это нужно, я обычно:
- вытащить определение перечисления в мой источник
- отредактируйте его, чтобы получить только имена
- сделать макрос, чтобы изменить имя на предложение case в вопросе, хотя обычно в одной строке: case foo: return "foo";
- добавить переключатель, по умолчанию и другой синтаксис, чтобы сделать его легальным
Следующий скрипт ruby пытается проанализировать заголовки и создает необходимые источники вместе с исходными заголовками.
#! /usr/bin/env ruby
# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs
GLOBS = [
"toto/*.h",
"tutu/*.h",
"tutu/*.hxx"
]
enums = {}
GLOBS.each { |glob|
Dir[glob].each { |header|
enums[header] = File.open(header, 'rb') { |f|
f.read
}.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
[
enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
enum_key_and_value.split(/\s*=\s*/).first
}
]
}
}
}
# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'
template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1
#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);
#endif
EOS
template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"
char* enum_to_string(<%= enum_name %> e)
{
switch (e)
{<% enum_keys.each do |enum_key| %>
case <%= enum_key %>: return "<%= enum_key %>";<% end %>
default: return "INVALID <%= enum_name %> VALUE";
}
}
EOS
enums.each { |header, enum_name_and_keys|
enum_name_and_keys.each { |enum_name, enum_keys|
File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
built_h.write(template_h.result(binding))
}
File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
built_cpp.write(template_cpp.result(binding))
}
}
}
Использование регулярных выражений делает этот "парсер" довольно хрупким, он может не справиться с вашими конкретными заголовками.
Допустим, у вас есть заголовок toto / ah, содержащий определения для перечислений MyEnum и MyEnum2. Скрипт будет строить:
toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp
Более надежные решения будут:
- Создайте все источники, определяющие перечисления и их операции, из другого источника. Это означает, что вы будете определять свои перечисления в XML/YML/ любом другом файле, который гораздо проще анализировать, чем в C/C++.
- Используйте настоящий компилятор, такой как предложенный Avdi.
- Используйте макросы препроцессора с шаблонами или без них.
Я столкнулся с этим вопросом, когда искал решение моей собственной проблемы для печати "слов" перечисления в C++. Я вернулся, чтобы предоставить простое решение, которое отвечает на поставленный вопрос в сформулированном виде. Все, что требуется, это "отразить" список перечисления вектором.
enum class genre { Fiction, NonFiction, Periodical, Biography, Children };
vector<string>genre_tbl { "Fiction", "NonFiction", "Periodical", "Biography", "Children" };
Поскольку перечисление, как указано выше, будет делать следующее по умолчанию;
Fiction = 0
NonFiction = 1
Periodical = 2
Biography = 3
Children = 4
Это соответствует позициям вектора, что делает перечисление в строку довольно простым.
string s1 = genre_tbl[int(genre::fiction)];
Для моей проблемы я создал пользовательский класс Book с членом Gen типа type. Программа должна была иметь возможность печатать жанр как слово.
class book {...};
ostream& operator<<(ostream& os, genre g) { return os << genre_tbl[int(g)]; }
book b1;
b1.Gen = genre(0)
cout << b1.Gen;
Для которого "Fiction" будет печатать на экране в этом случае.
Это практически единственный способ сделать это (массив строк также может работать).
Проблема заключается в том, что после компиляции программы на C двоичное значение перечисления - это все, что используется, и имя исчезает.
#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)
std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
mapEnumtoString(){ }
mapEnumtoString& operator()(int i,std::string str)
{
enToStr[i] = str;
return *this;
}
public:
std::string operator [] (int i)
{
return enToStr[i];
}
};
mapEnumtoString k;
mapEnumtoString& init()
{
return k;
}
int main()
{
init()
IDMAP(1)
IDMAP(2)
IDMAP(3)
IDMAP(4)
IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}
Как вариант, используйте простую lib> http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C
В коде
#include <EnumString.h>
enum FORM {
F_NONE = 0,
F_BOX,
F_CUBE,
F_SPHERE,
};
добавить строки
Begin_Enum_String( FORM )
{
Enum_String( F_NONE );
Enum_String( F_BOX );
Enum_String( F_CUBE );
Enum_String( F_SPHERE );
}
End_Enum_String;
Работает нормально, если значения в enum не дублируются.
Пример использования
enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );
и наоборот
assert( EnumString< FORM >::To( f, str ) );