Можно преобразовать список #defines в строки

Предположим, что у меня есть список #define в файле заголовка для внешней библиотеки. Эти #define представляют собой коды ошибок, возвращаемые функциями. Я хочу написать функцию преобразования, которая может принимать в качестве входного кода код ошибки и возвращать в качестве вывода строковый литерал, представляющий фактическое имя #define.

В качестве примера, если у меня есть

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2

Я хотел бы, чтобы функция могла вызываться как

int errorCode = doSomeLibraryFunction();
if (errorCode)
    writeToLog(convertToString(errorCode));

И пусть convertToString() сможет автоматически конвертировать этот код ошибки, не будучи гигантским корпусом коммутатора, похожим на

const char* convertToString(int errorCode)
{
    switch (errorCode)
    {
        case NO_ERROR:
           return "NO_ERROR";
        case ONE_KIND_OF_ERROR:
           return "ONE_KIND_OF_ERROR";
        ...
     ...
...

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

Ответ 1

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

#define STR(code) case code: return #code
switch (errorCode)
{
    STR(NO_ERROR);
    STR(ONE_KIND_OF_ERROR);
}

Это хороший вопрос, мне интересно узнать, что лучше у людей

Ответ 2

Другой способ сделать это популярным в сгенерированном коде:

#define NO_ERROR 0
#define ONE_KIND_OF_ERROR 1
#define ANOTHER_KIND_OF_ERROR 2
static const char* const error_names[] = {"NO_ERROR", "ONE_KIND_OF_ERROR", "ANOTHER_KIND_OF_ERROR"};

const char* convertToString(int errorCode) {return error_names[errorCode];}

Я предпочитаю вариант case switch я уже упомянутый, но в зависимости от того, как структурирован ваш код, может быть проще, поскольку часть процесса сборки может автоматически генерировать этот массив

Ответ 3

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

Ответ 4

взгляните на ускоритель препроцессора. Вы можете создать список/массив/последовательность пар кода:

#define codes ((1,"code1"))((...))

#define code1 1
...

// then use preprocessor FOR_EACH to generate error handlers

релевантная ссылка:

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/seq_for_each.html

http://www.boost.org/doc/libs/1_41_0/libs/preprocessor/doc/ref/tuple_elem.html

Ответ 5

Одна из возможностей здесь - написать небольшую программу, которая анализирует файл .h, содержащий #defines, и испускает соответствующий исходный код для функции convertToString(). Затем вы можете автоматически запустить эту программу как часть процесса сборки, когда будет изменен файл .h. Он немного больше работает, но как только он будет реализован, вам больше не понадобится вручную обновлять функцию преобразования строки int ↔ .

Это особенно полезно, если вы хотите поддерживать код на нескольких языках, которые используют одни и те же константы. Например, в одном из моих приложений изменение моего файла заголовка #defines приводит к автоматическому восстановлению соответствующих файлов С++, Python и Java. Это упрощает и упрощает обслуживание проекта.

Ответ 6

Если вы определенно хотите сохранить #define, я бы пошел с Майклом изящным ответом #define STR(code). Но определяет больше C, чем С++, и большой недостаток для определения - вы не можете помещать их в пространство имен. Они будут загрязнять глобальное пространство имен любой программы, в которую вы их включили. Если это вам поможет изменить ее, я бы рекомендовал вместо этого использовать анонимное перечисление:

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

Это то же самое, что и у #define, и вы можете поместить его в пространство имен. И теперь вы можете использовать ответ Майкла, связанный с массивом static const char* const error_names, чтобы выполнить то, что вы изначально задали.

Ответ 7

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

Первое, что, конечно, состоит в том, что #define не следует использовать для констант, перечислимое будет, вероятно, лучше, однако перечисление не может быть расширено, что требует, чтобы все ваши ошибки были определены в одном и том же месте (ouch, thank вы так много для зависимостей...)

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

Сначала нам нужно определить класс Handler (не обязательно встраивать все это, но это проще для примеров)

#include <boost/bimap.hpp>
#include <boost/optional.hpp>

namespace error
{

  class Handler
  {
  public:
    typedef boost::optional<int> return_code;
    typedef boost::optional<std::string> return_description;

    static bool Register(int code, const char* description)
    {
      typedef error_map::value_type value_type;
      bool result = MMap().insert(value_type(code,description)).second;

      // assert(result && description)
      return result;
    }

    static return_code GetCode(std::string const& desc)
    {
      error_map::map_by<description>::const_iterator it
          = MMap().by<description>().find(desc);
      if (it != MMap().by<description>().end()) return it->second;
      else return return_code();
    }

    static return_description GetDescription(int c)
    {
      error_map::map_by<code>::const_iterator it
          = MMap().by<code>().find(c);
      if (it != MMap().by<code>().end()) return it->second;
      else return return_description();
    }

    typedef std::vector< std::pair<int,std::string> > errors_t;
    static errors_t GetAll()
    {
      errors_t result;
      std::for_each(MMap().left.begin(), MMap().left.end(),
                    result.push_back(boost::lambda::_1));
      return result;
    }

  private:
    struct code {};
    struct description {};

    typedef boost::bimap<
      boost::tagged<int, code>,
      boost::tagged<std::string, description>
    > error_map;

    static error_map& Map() { static error_map MMap; return MMap; }
  };

  // Short-Hand
  boost::optional<int> GetCode(std::string const& d)
  {
    return Handler::GetCode(d);
  }

  boost::optional<std::string> GetDescription(int c)
  { 
    return Handler::GetDescription(c);
  }
} // namespace error

Тогда нам просто нужно предоставить некоторый синтаксический сахар:

#define DEFINE_NEW_ERROR(Code_, Description_)            \
  const int Description_ = Code_;                        \
  namespace error {                                      \
    const bool Description##_Registered =                \
      ::error::Handler::Register(Code_, #Description_);  \
  }

Мы можем быть немного более жестокими в случае регистрации неизвестной ошибки (например, утверждать).

И тогда мы всегда можем перенести этот макрос в один, который может определять несколько символов за один раз... но это оставлено как упражнение.

Использование:

// someErrors.hpp
#include "error/handler.hpp"

DEFINE_NEW_ERROR(1, AnError)
DEFINE_NEW_ERROR(2, AnotherError)

// someFile.cpp
#include <iostream>
#include "error/handler.hpp"

int main(int argc, char* argv[])
{
  int code = 6;
  boost::optional<std::string> desc = error::GetDescription(code);

  if (desc)
  {
    std::cout << "Code " << code << " is mapped to <" << *desc << ">" << std::endl;
  }
  else
  {
    std::cout << "Code " << code << " is unknown, here is the list:\n";

    ::error::Handler::errors_t errors = ::Error::Handler::GetAll();

    std::for_each(errors.begin(), errors.end(), std::cout << "  " << _1);

    std::cout << std::endl;
  }
}

Отказ от ответственности: я не слишком уверен в синтаксисе лямбда, но он упростил письмо.

Ответ 8

#define FOO 1 обрабатывается препроцессором как простая замена текста. Если вы хотите сохранить эти определения, вам нужен гигантский переключатель.