Существует ли простой способ преобразовать перечисление C++ для строкового представления?

118
задан Itay Grudev 15 December 2015 в 05:54
поделиться

15 ответов

Можно хотеть проверить 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 и генерировать Ваш желаемый код.

48
ответ дан Avdi 24 November 2019 в 01:56
поделиться

Это - в значительной степени единственный способ, которым это может быть сделано (массив строки мог работать также).

проблема, как только программа C компилируется, двоичное значение перечисления - все, что используется, и имени не стало.

0
ответ дан James Curran 24 November 2019 в 01:56
поделиться

Следующий рубиновый сценарий пытается проанализировать заголовки и 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] надежные решения были бы:

  • Сборка все источники, определяющие перечисления и их операции из другого источника. Это означает определение перечислений в XML/YML/whatever файле, который намного легче проанализировать, чем C/C++.
  • Использование реальный компилятор такой, как предложено Avdi.
  • макросы препроцессора Использования с или без шаблонов.
1
ответ дан bltxd 24 November 2019 в 01:56
поделиться

Проблема с ответом 0 состоит в том, что перечислимые двоичные значения не обязательно запускаются в 0 и не обязательно непрерывны.

, Когда мне нужно это, я обычно:

  • вытягивают перечислимое определение в мой источник
  • редактирование, это для получения просто имен
  • делает макрос, чтобы изменить имя на пункт случая в вопросе, хотя обычно на одной строке: нечто случая: возвратите "нечто";
  • добавляют переключатель, значение по умолчанию и другой синтаксис для создания его законным
1
ответ дан mpez0 24 November 2019 в 01:56
поделиться

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.
73
ответ дан Marcin Koziuk 24 November 2019 в 01:56
поделиться

@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)
};
42
ответ дан Jasper Bekkers 24 November 2019 в 01:56
поделиться

То, что я склонен делать, создают массив 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. К сожалению, это не будет работать на переменную (поскольку Вы распечатаете имя переменной)

33
ответ дан greatwolf 24 November 2019 в 01:56
поделиться

QT в состоянии вытянуть тот из (благодаря компилятору метаобъекта): ссылка

8
ответ дан Ronny Brendel 24 November 2019 в 01:56
поделиться

Другой ответ: в некоторых контекстах имеет смысл определять Ваше перечисление в неформате кода, как CSV, YAML или XML-файл, и затем генерировать и код перечисления C++ и к коду строки из определения. Этот подход может или не может быть практичным в Вашем приложении, но это - что-то для учета.

3
ответ дан Avdi 24 November 2019 в 01:56
поделиться

макро-решение 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

4
ответ дан MSalters 24 November 2019 в 01:56
поделиться

Обратите внимание, что Ваша функция преобразования должна идеально возвращаться константа символ *.

, Если можно позволить себе поместить перечисления в их отдельные заголовочные файлы, Вы могли бы, возможно, сделать что-то вроде этого с макросами (о, это будет ужасно):

#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));

Это ужасно, но это - единственный путь (на уровне препроцессора), который позволяет Вам определить свое перечисление только в единственном месте в Вашем коде. Ваш код поэтому не подвержен ошибкам из-за модификаций к перечислению. Ваше перечислимое определение и функция преобразования всегда будут в синхронизации. Однако я повторяюсь, это ужасно:)

2
ответ дан Ates Goral 24 November 2019 в 01:56
поделиться

Я делаю это с отдельным бок о бок перечислимые классы обертки, которые сгенерированы с макросами. Существует несколько преимуществ:

  • Может генерировать их для перечислений, которые я не определяю (например: перечисления заголовка платформы ОС)
  • Может соединиться, проверка диапазона в класс
  • обертки Может сделать "более умное" форматирование с перечислениями битового поля

, оборотная сторона, конечно, то, что я должен копировать перечислимые значения в классах средства форматирования, и у меня нет сценария для генерации их. Кроме этого, тем не менее, это, кажется, работает вполне прилично.

Вот пример перечисления от моей кодовой базы без всего кода платформы, который реализует макросы и шаблоны, но можно получить идею:

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 это полезно. Я понимаю, что это также не обращается к исходному вопросу о сценарии для фактической генерации другого класса, но я надеюсь, что структура помогает кому-то пытающемуся решить ту же проблему или записать такой сценарий.

2
ответ дан Nick 24 November 2019 в 01:56
поделиться

Интересно увидеть количество способов. вот тот, который я использовал давным-давно:

в файле 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;
}
5
ответ дан 24 November 2019 в 01:56
поделиться

Это неизданное программное обеспечение, но похоже, что BOOST_ENUM от Фрэнка Лауба может удовлетворить все требования. Что мне нравится в этом, так это то, что вы можете определить перечисление в рамках класса, чего обычно не позволяют делать перечисления на основе макросов. Он находится в Boost Vault по адресу: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& Он не разрабатывался с 2006 года, поэтому я не знаю, насколько хорошо он компилируется с новыми выпусками Boost. Посмотрите в libs / test пример использования.

1
ответ дан 24 November 2019 в 01:56
поделиться

Вот программа CLI, которую я написал, чтобы легко преобразовывать перечисления в строки. Его легко использовать, и для его выполнения требуется около 5 секунд (включая время, необходимое для перехода к каталогу, содержащему программу, затем запустить его, передав ему файл, содержащий перечисление).

Скачать здесь: http://www.mediafire.com/?nttignoozzz

Тема обсуждения по этому поводу здесь: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

Запустите программу с аргументом «--help», чтобы получить описание как это использовать.

0
ответ дан 24 November 2019 в 01:56
поделиться
Другие вопросы по тегам:

Похожие вопросы: