Предположим, у нас есть несколько именованных перечислений:
enum MyEnum {
FOO,
BAR = 0x50
};
Я искал в Google сценарий (на любом языке), который сканирует все заголовки в моем проекте и генерирует заголовок с одной функцией для каждого перечисления.
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";
}
}
Проблема на самом деле связана с перечислениями с определением типа и безымянными перечислениями в стиле C. Кто-нибудь знает что-нибудь по этому поводу?
Обновлено: решение не должно изменять мой источник, за исключением сгенерированных функций. Перечисления находятся в API, поэтому использование предложенных до сих пор решений просто не вариант.
Ответ о фабрике на основе макросов перенесен в stackoverflow.com/questions/147267/… - после того, как вопрос был обновлен, здесь он больше не актуален.





Это практически единственный способ сделать это (также может работать массив строк).
Проблема в том, что после компиляции программы на C используется только двоичное значение перечисления, а имя пропадает.
Я стараюсь создать массив C с именами в том же порядке и позиции, что и значения перечисления.
например.
enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };
тогда вы можете использовать массив в тех местах, где вам нужно удобочитаемое значение, например
colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];
Вы можете немного поэкспериментировать с оператором преобразования строк (см. # В справочнике по препроцессору), который в некоторых случаях будет делать то, что вы хотите, например:
#define printword(XX) cout << #XX;
printword(red);
выведет "красный" на стандартный вывод. К сожалению, это не сработает для переменной (так как вы получите распечатанное имя переменной)
Последнее предостережение (не сработает для переменной) - большой недостаток, но +1 все равно.
Работает только в том случае, если вы не будете устанавливать специальные числовые значения для записей перечисления.
Проблема с ответом 0 заключается в том, что двоичные значения перечисления не обязательно начинаются с 0 и не обязательно являются смежными.
Когда мне это нужно, я обычно:
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.
изящно, хотя мне не нравится лишний файл
Просто убедитесь, что ваш процесс сборки не добавляет #pragma (once) перед каждым включаемым файлом ...
Проблема с этим решением заключается в том, что он изменяет заголовок, определяющий перечисление, что я не могу сделать. Так что для моей проблемы этого недостаточно.
Я не уверен в "лучшем" решении!
Это решение намного превосходит любое решение на основе переключателя или массива, потому что оно не дублирует имена, что упрощает изменение перечисления.
Одно замечание: в последнем фрагменте кода примера отсутствует хеш. Это должно быть: #define X (a, b) a, #define X (a, b) #b, И первая строка также может быть: #define X (a) a,
@ ikku100 вы неверны насчет #define X(a, b) #b. Это необходимо только в том случае, если определение выглядит как X(Red, red), а не как определение, показанное в ответе X(Red, "red").
Потрясающий! @RonnyBrendel, небольшая настройка может сделать его однофайловым решением (см. Мой ответ ниже)
Я думаю, что это также можно адаптировать с помощью #define X(a) case a: return #a; и char *color_str(Colours c){ switch(c){ #define, #include, #undef, default: return "error"; }, и таким образом он будет обрабатывать перечисления с непоследовательными целочисленными значениями.
Макрорешение Suma - это хорошо. Однако вам не обязательно иметь два разных макроса. C++ с радостью включит заголовок дважды. Просто оставьте включенную охрану.
Таким образом, у вас будет foobar.h, определяющий только
ENUM(Foo, 1)
ENUM(Bar, 2)
и вы бы включили это так:
#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"
enumfactory.h выполнит 2 #include ENUMFACTORY_ARGUMENT. В первом раунде он расширяет ENUM, как DECLARE_ENUM от Suma; во втором раунде ENUM работает как DEFINE_ENUM.
Вы также можете включать enumfactory.h несколько раз, если вы передаете разные # define для ENUMFACTORY_ARGUMENT
похоже сума переместила ответ здесь. Вы можете включить ссылку в свой ответ. Я нашел комментарий случайно, и без сумм ответа, этот довольно бессмысленный
Вы можете проверить 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 и сгенерировать желаемый код.
Превосходно! Работал как брелок с простым скриптом на питоне. Спасибо.
+1, GCCXML выглядит очень красиво! (Хотя я почти упал, поскольку изначально неправильно прочитал это как предложение использовать приведенный выше подробный синтаксис XML для кодирования вашего перечисления - решение, которое пахнет чрезмерной инженерией!)
какие изменения вы можете опубликовать в скрипте Python?
Обратите внимание, что в идеале ваша функция преобразования должна возвращать символ const *.
Если вы можете позволить себе поместить свои перечисления в отдельные файлы заголовков, возможно, вы могли бы сделать что-то подобное с макросами (о, это будет некрасиво):
#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));
Это уродливо, но это единственный способ (на уровне препроцессора), который позволяет вам определять перечисление только в одном месте вашего кода. Таким образом, ваш код не подвержен ошибкам из-за изменений в перечислении. Ваше определение перечисления и функция преобразования всегда будут синхронизированы. Однако, повторяю, это некрасиво :)
Следующий скрипт 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 / a.h, содержащий определения для перечислений MyEnum и MyEnum2. Скрипт построит:
toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp
Более надежными решениями были бы:
Другой ответ: в некоторых контекстах имеет смысл определить ваше перечисление в некодовом формате, таком как файл CSV, YAML или XML, а затем сгенерировать как код перечисления C++, так и код преобразования из определения. Такой подход может оказаться практичным или непрактичным в вашем приложении, но об этом следует помнить.
QT может вытащить это из (благодаря компилятору метаобъектов):
QNetworkReply::NetworkError error;
error = fetchStuff();
if (error != QNetworkReply::NoError) {
QString errorValue;
QMetaObject meta = QNetworkReply::staticMetaObject;
for (int i=0; i < meta.enumeratorCount(); ++i) {
QMetaEnum m = meta.enumerator(i);
if (m.name() == QLatin1String("NetworkError")) {
errorValue = QLatin1String(m.valueToKey(error));
break;
}
}
QMessageBox box(QMessageBox::Information, "Failed to fetch",
"Fetching stuff failed with error '%1`").arg(errorValue),
QMessageBox::Ok);
box.exec();
return 1;
}
In Qt every class that has the Q_OBJECT macro will automatically have a static member "staticMetaObject" of the type QMetaObject. You can then find all sorts of cool things like the properties, signals, slots and indeed enums.
Я делаю это с помощью отдельных классов-оболочек перечисления бок о бок, которые генерируются с помощью макросов. Есть несколько преимуществ:
Обратной стороной, конечно же, является то, что мне нужно дублировать значения перечисления в классах средства форматирования, и у меня нет сценария для их генерации. Однако, в остальном, похоже, он работает довольно хорошо.
Вот пример перечисления из моей кодовой базы без всего кода фреймворка, который реализует макросы и шаблоны, но вы можете понять:
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 (...).
Надеюсь, это будет полезно. Я понимаю, что это также не решает исходный вопрос о сценарии для фактического создания другого класса, но я надеюсь, что структура поможет кому-то, пытающемуся решить ту же проблему или написать такой сценарий.
@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)
};
Мне нравится это решение. Было бы яснее, если бы SOME_UNION и MAKE_UNION назывались SOME_ENUM и MAKE_ENUM.
Это отличное решение. У меня самый удобный в обслуживании менеджер ресурсов C++, с которым я когда-либо имел дело.
Я должен поблагодарить вас за это простое решение :-) - Я немного изменил его, чтобы MetaSyntacticVariableNames[] стал частью объявления класса, создав метод static const char* getNameByEnum(MetaSyntacticVariable e) { /*code to return the static string*/ }
Фантастический ответ! Я еще больше упростил его, сгруппировав MAKE_ENUM и MAKE_STRINGS в один макрос, что сделало весь процесс еще проще. Я добавил ответ в этой теме с этим кодом, если кому-то интересно.
Интересно увидеть количество способов. вот тот, который я использовал давным-давно:
в файле 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. Это константа, не наследует контейнер 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;
}
Это совершенно законно. Я делаю это все время.
Хорошее решение. Это С ++, поэтому использовать карту stl можно.
Это неизданное программное обеспечение, но похоже, что BOOST_ENUM от Фрэнка Лауба может соответствовать всем требованиям. Что мне нравится в этом, так это то, что вы можете определить перечисление в рамках класса, чего обычно не позволяет делать большинство перечислений на основе макросов. Он находится в Boost Vault по адресу: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=&. Он не разрабатывался с 2006 года, поэтому я не знаю, насколько хорошо он компилируется с новыми выпусками Boost. Посмотрите в libs / test пример использования.
Вот программа CLI, которую я написал, чтобы легко преобразовывать перечисления в строки. Его легко использовать, и на выполнение требуется около 5 секунд (включая время, необходимое для перехода к каталогу, содержащему программу, затем запустить ее, передав ей файл, содержащий перечисление).
Скачать здесь: http://www.mediafire.com/?nttignoozzz
Тема обсуждения здесь: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html
Запустите программу с аргументом «--help», чтобы получить описание того, как ее использовать.
Не могли бы вы поместить это где-нибудь в репозиторий (github, google code или bitbucket) и разместить здесь ссылку вместо mediafire? Я бы помог людям, желающим это понять :)
Я только что заново изобрел это колесо сегодня и подумал, что поделюсь им.
Эта реализация не требует от нет каких-либо изменений в коде, определяющем константы, которые могут быть перечислениями, #define или чем-либо еще, переходящим в целое число - в моем случае у меня были символы, определенные в терминах других символов. Он также хорошо работает с разреженными значениями. Он даже позволяет использовать несколько имен для одного и того же значения, всегда возвращая первое. Единственным недостатком является то, что вам потребуется создать таблицу констант, которая может устареть, например, при добавлении новых.
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;
}
}
Хорошее решение. Но я бы предпочел switch and case, так как он прост и понятен.
#define stringify( name ) # name
enum MyEnum {
ENUMVAL1
};
...stuff...
stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1
Дальнейшее обсуждение этого метода
Уловки с директивой препроцессора для новичков
На самом деле это довольно бесполезно, поскольку метод stringify находится во время компиляции и является буквальным. Если вы говорите, что у вас есть рассматриваемый тип перечисления внутри переменной, попытка преобразовать переменную в строку даст вам просто имя переменной, а не имя типа перечисления.
Не так давно я проделал хитрость, чтобы перечисления правильно отображались в QComboBox и определяли представления перечислений и строк как одну инструкцию.
#pragma once
#include <boost/unordered_map.hpp>
namespace enumeration
{
struct enumerator_base : boost::noncopyable
{
typedef
boost::unordered_map<int, std::wstring>
kv_storage_t;
typedef
kv_storage_t::value_type
kv_type;
kv_storage_t const & kv() const
{
return storage_;
}
LPCWSTR name(int i) const
{
kv_storage_t::const_iterator it = storage_.find(i);
if (it != storage_.end())
return it->second.c_str();
return L"empty";
}
protected:
kv_storage_t storage_;
};
template<class T>
struct enumerator;
template<class D>
struct enum_singleton : enumerator_base
{
static enumerator_base const & instance()
{
static D inst;
return inst;
}
};
}
#define QENUM_ENTRY(K, V, N) K, N storage_.insert(std::make_pair((int)K, V));
#define QBEGIN_ENUM(NAME, C) \
enum NAME \
{ \
C \
} \
}; \
} \
#define QEND_ENUM(NAME) \
}; \
namespace enumeration \
{ \
template<> \
struct enumerator<NAME>\
: enum_singleton< enumerator<NAME> >\
{ \
enumerator() \
{
//usage
/*
QBEGIN_ENUM(test_t,
QENUM_ENTRY(test_entry_1, L"number uno",
QENUM_ENTRY(test_entry_2, L"number dos",
QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/
Теперь у вас есть enumeration::enum_singleton<your_enum>::instance(), способный преобразовывать перечисления в строки. Если вы замените kv_storage_t на boost::bimap, вы также сможете выполнить обратное преобразование.
Был введен общий базовый класс для конвертера, чтобы хранить его в объекте Qt, потому что объекты Qt не могли быть шаблонами.
Как вариант, используйте простую 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 ) );
#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);
Мне нравится ваш API, но, к сожалению, ваш SmartEnum на самом деле не создает «тип» перечисления. Вы не можете сделать MyEnum x = MyEnum::TWO;. Я опубликовал свою правку вашего класса, чтобы поддержать это.
Вот попытка автоматически получить операторы потока << и >> в enum с помощью только однострочной макрос-команды ...
Определения:
#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str
#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)
#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
attribute std::istream& operator>>(std::istream& is, name& e) { \
const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
std::string str; \
std::istream& r = is >> str; \
const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
const std::vector<std::string> enumStr(name##Str, name##Str + len); \
const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
if (it != enumStr.end())\
e = name(it - enumStr.begin()); \
else \
throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
return r; \
}; \
attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
return (os << name##Str[e]); \
}
Использование:
// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);
class Essai {
public:
// Declare enum inside class
enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);
};
int main() {
std::cout << Essai::Item1 << std::endl;
Essai::Test ddd = Essai::Item1;
std::cout << ddd << std::endl;
std::istringstream strm("Item2");
strm >> ddd;
std::cout << (int) ddd << std::endl;
std::cout << ddd << std::endl;
}
Не уверен в ограничениях этой схемы ... комментарии приветствуются!
#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];
}
Пожалуйста, объясните, почему это ответ.
У меня есть невероятно простой в использовании макрос, который делает это совершенно СУХИМ способом. Он включает в себя вариативные макросы и некоторую простую магию синтаксического анализа. Вот оно:
#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
);
Хорошая идея - использовать строго типизированное перечисление (класс перечисления). Вот демонстрация: cpp.sh/4ife
Работает ли это с внешне определенными перечислениями / символами. Например, символы, определенные ОС или библиотекой, с пробелами в нумерации?
Очень красиво, но не компилируется, если поместить внутрь класса (я не мог понять, почему).
Мне не удалось скомпилировать это в VS2015. Я получаю предупреждение и ошибку: предупреждение: многострочный комментарий [-Wcomment] #define MAKE_ENUM (name, ...) enum class name {VA_ARGS, __COUNT} error: stray '#' in program std *: string enumName = #имя
Это модификация ответа @ user3360260. Он имеет следующие новые функции
MyEnum fromString(const string&) поддержкаИспользование:
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 - это быстрый поиск, а преобразование fromString - это медленный линейный поиск. Но строки в любом случае настолько дороги (и связанный с ними ввод-вывод файла), что я не чувствовал необходимости оптимизировать или использовать bimap.
У вас и user3360260 есть хорошее решение. Почему бы вместо этого не использовать мультикарту?
Это можно сделать в 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;
}
Это не отвечает на вопрос OP: он искал способ автоматически генерировать функции, чтобы вернуть имя члена перечисления в виде строки.
Здесь однофайловое решение (на основе элегантного ответа @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;
}
Это было мое решение с 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()); \
}
Чтобы создать перечисление, объявите:
STR_ENUM
(
SERVICE_RELOAD,
(reload_log)
(reload_settings)
(reload_qxml_server)
)
Для конверсий:
SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);
Я хочу опубликовать это на случай, если кто-то сочтет это полезным.
В моем случае мне просто нужно сгенерировать функции ToString() и FromString() для одного перечисления C++ 11 из одного файла .hpp.
Я написал сценарий Python, который анализирует файл заголовка, содержащий элементы перечисления, и генерирует функции в новом файле .cpp.
Вы можете добавить этот сценарий в CMakeLists.txt с помощью execute_process или как событие перед сборкой в Visual Studio. Файл .cpp будет создан автоматически, без необходимости вручную обновлять его каждый раз, когда добавляется новый элемент перечисления.
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;
}
}
Вот онлайн-генератор: th-thielemann.de/tools/cpp-enum-to-string.html
Что ж, еще один вариант. Типичный вариант использования - когда вам нужна константа для HTTP-глаголов, а также используются строковые значения версии.
Пример:
int main () {
VERB a = VERB::GET;
VERB b = VERB::GET;
VERB c = VERB::POST;
VERB d = VERB::PUT;
VERB e = VERB::DELETE;
std::cout << a.toString() << std::endl;
std::cout << a << std::endl;
if ( a == VERB::GET ) {
std::cout << "yes" << std::endl;
}
if ( a == b ) {
std::cout << "yes" << std::endl;
}
if ( a != c ) {
std::cout << "no" << std::endl;
}
}
Класс VERB:
// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {
private:
// private constants
enum Verb {GET_=0, POST_, PUT_, DELETE_};
// private string values
static const std::string theStrings[];
// private value
const Verb value;
const std::string text;
// private constructor
VERB (Verb v) :
value(v), text (theStrings[v])
{
// std::cout << " constructor \n";
}
public:
operator const char * () const { return text.c_str(); }
operator const std::string () const { return text; }
const std::string toString () const { return text; }
bool operator == (const VERB & other) const { return (*this).value == other.value; }
bool operator != (const VERB & other) const { return ! ( (*this) == other); }
// ---
static const VERB GET;
static const VERB POST;
static const VERB PUT;
static const VERB DELETE;
};
const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};
const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file
Использование составных тернарных операторов может быть несколько элегантным для перечислений с небольшим количеством элементов (однострочными). Выражение также увеличивается приблизительно линейно по длине с количеством элементов.
Вот хороший пример использования:
enum log_level {INFO, WARNING, ERROR};
...
void logger::write(const std::string log, const log_level l) {
...
std::string s = (l == INFO) ? "INFO" :
(l == WARNING) ? "WARNING" :
(l == ERROR) ? "ERROR" : "UNKNOWN";
...
}
...
Конечно, это просто еще один блок операторов switch / if, но это однострочный оператор. А если говорить о лаконичности и простоте, то она встречается где-то посередине. Как постоянное выражение, его также можно легко превратить во встроенную функцию.
Я столкнулся с этим вопросом, когда искал решение своей проблемы с печатью «слов» перечисления на 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 типа жанра. Программа должна была иметь возможность печатать жанр как слово.
class book {...};
ostream& operator<<(ostream& os, genre g) { return os << genre_tbl[int(g)]; }
book b1;
b1.Gen = genre(0)
cout << b1.Gen;
Для чего «Художественная литература» в этом случае будет выводиться на экран.
Добавление еще большей простоты использования Фантастический ответ Джаспера Беккерса:
Настроить один раз:
#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)
Вы можете использовать библиотеку отражения, например Размышлять. Вы регистрируете перечисления, а затем можете конвертировать их туда и обратно с помощью 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"
Этот вопрос дублирует,
Однако ни на один из вопросов я не смог найти хороших ответов.
Вникнув в тему, я нашел два отличных решения с открытым исходным кодом:
Примечание: я повторяю рекомендацию здесь. У этого вопроса много трафика / просмотров, и он действительно требует перечисления приведенных выше решений.
возможный дубликат Простой способ использовать переменные перечислимого типа как строку в C?