Можно хотеть проверить 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>
Вы могли использовать любой язык, Вы предпочитаете вытаскивать теги Перечисления и EnumValue и генерировать Ваш желаемый код.
Это - в значительной степени единственный способ, которым это может быть сделано (массив строки мог работать также).
проблема, как только программа C компилируется, двоичное значение перечисления - все, что используется, и имени не стало.
Следующий рубиновый сценарий пытается проанализировать заголовки и builts необходимые источники вместе с исходными заголовками.
#! /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
[еще 118] надежные решения были бы:
Проблема с ответом 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.
@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 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);
распечатает "красный" к stdout. К сожалению, это не будет работать на переменную (поскольку Вы распечатаете имя переменной)
QT в состоянии вытянуть тот из (благодаря компилятору метаобъекта): ссылка
Другой ответ: в некоторых контекстах имеет смысл определять Ваше перечисление в неформате кода, как CSV, YAML или XML-файл, и затем генерировать и код перечисления C++ и к коду строки из определения. Этот подход может или не может быть практичным в Вашем приложении, но это - что-то для учета.
макро-решение Suma хорошо. У Вас не должно быть двух различных макросов, все же. C++ wil счастливо включает заголовок дважды. Просто не учтите включать защиту.
, Таким образом, у Вас был бы foobar.h, определяющий всего
ENUM(Foo, 1)
ENUM(Bar, 2)
и Вы будете включать его как это:
#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"
enumfactory.h сделает 2 #include ENUMFACTORY_ARGUMENT
с. В первом раунде это разворачивает ПЕРЕЧИСЛЕНИЕ как Suma DECLARE_ENUM
; во втором раунде ПЕРЕЧИСЛЕНИЕ работает как DEFINE_ENUM
.
можно включать enumfactory.h многократно также, пока Вы передаете в различном #define для ENUMFACTORY_ARGUMENT
Обратите внимание, что Ваша функция преобразования должна идеально возвращаться константа символ *.
, Если можно позволить себе поместить перечисления в их отдельные заголовочные файлы, Вы могли бы, возможно, сделать что-то вроде этого с макросами (о, это будет ужасно):
#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 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; все работает то же, но Вы получаете проверку диапазона и 'Формат ()' метод на самой перечислимой переменной. Если необходимо отформатировать автономное значение, можно использовать CEnumFormatter_EHelpLocation:: FormatEnum (...).
Hope это полезно. Я понимаю, что это также не обращается к исходному вопросу о сценарии для фактической генерации другого класса, но я надеюсь, что структура помогает кому-то пытающемуся решить ту же проблему или записать такой сценарий.
Интересно увидеть количество способов. вот тот, который я использовал давным-давно:
в файле 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;
}
Это неизданное программное обеспечение, но похоже, что 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», чтобы получить описание как это использовать.