可以将 #defines 列表转换为字符串

发布于 2024-08-29 13:51:14 字数 786 浏览 3 评论 0 原文

假设我在外部库的头文件中有一个#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";
        ...
     ...
...

我 一样是一个巨大的开关盒一种感觉是,如果这是可能的,那么使用模板和元编程是可能的,但这只能起作用,错误代码实际上是一种类型,而不是一堆处理器宏。

Suppose I have a list of #defines in a header file for an external library. These #defines represent error codes returned from functions. I want to write a conversion function that can take as an input an error code and return as an output a string literal representing the actual #define name.

As an example, if I have

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

I would like a function to be able to called like

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

And have convertToString() be able to auto-convert that error code without being a giant switch-case looking like

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

I have a feeling that if this is possible, it would be possible using templates and metaprogramming, but that would only work the error codes were actually a type and not a bunch of processor macros.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

巷雨优美回忆 2024-09-05 13:51:15

如果您确实想保留 #define,我会选择 Michael 优雅的 #define STR(code) 答案。但是,define 更像是 C 语言,而不是 C++,define 的最大缺点是不能将它们放在命名空间中。它们会污染您包含它们的任何程序的全局命名空间。如果您有权更改它,我建议您使用匿名枚举:

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

这与您的#define完全相同有,你可以将它放在命名空间中。现在您可以使用 Michael 的其他答案,涉及 static const char* const error_names 数组来执行您最初要求的操作。

If you definitely want to keep the #define's I would go with Michael's elegant #define STR(code) answer. But defines are more C than C++, and the big downside to defines is you can't put them in a namespace. They will pollute the global namespace of any program you include them in. If it's in your power to change it, I would recommend using an anonymous enum instead:

enum{ NO_ERROR ONE_KIND_OF_ERROR ANOTHER_KIND_OF_ERROR }

This is exactly the same as the #defines you have, and you can put it in a namespace. And now you can use Michael's other answer involving the static const char* const error_names array yo do what you had originally asked.

夏末染殇 2024-09-05 13:51:15

实际上,您可以采用两种方式,即具有两个从代码来回转换为错误的函数。

当然,第一件事是 #define 不应该用于常量,枚举可能是最好的,但是枚举不能扩展,这要求所有错误都定义在同一个地方(哎呀,非常感谢您的依赖关系...)

不过,您可以再做一次,使用名称空间来隔离符号,并进行预处理来处理整个生成过程。对于查找部分,我们将使用 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;
  }
}

免责声明:我不太确定 lambda 语法,但它确实简化了编写。

You can actually have it both ways, ie having two functions that translate from code to error back and forth.

The first thing of course is that #define should not be used for constants, an enum would probably be best, however an enum cannot be extended, which requires that all your errors be defined in the same place (ouch, thank you so much for dependencies...)

You can do it another may though, using namespaces to isolate the symbols, and preprocessing to handle the whole generation. For the lookup part, we'll use a Bimap.

First we need to define a Handler class (not necessary to inline all that, but it's easier for examples)

#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

Then we just need to provide some syntactic sugar:

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

We could be a bit more violent in case of a registration of an unknown error (assert for example).

And then we can always wrap that macro into one that can define multiple symbols in one go... but that's left as an exercise.

Usage:

// 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;
  }
}

Disclaimer: I am not too sure about the lambda syntax, but it did simplified the writing.

ゞ记忆︶ㄣ 2024-09-05 13:51:15

#define FOO 1 由预处理器作为简单的文本替换进行处理。如果你想保留这些定义,你需要巨大的开关。

#define FOO 1 is handled by the preprocessor as a simple text replacement. If you want to preserve those definitions, you need the giant switch.

在梵高的星空下 2024-09-05 13:51:14

我通常用巨型开关盒的方式来做,尽管我用以下方法使它更容易:

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

这是一个很好的问题,我有兴趣看看人们有什么更好的方法

I normally do it the giant switch case way, although I make it somewhat easier with:

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

This is a good question, I'm interested to see what better ways people have

寂寞笑我太脆弱 2024-09-05 13:51:14

在生成的代码中流行的另一种方法是:

#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];}

我更喜欢 switch case 方式 已经提到,但根据代码的结构方式,作为构建过程的一部分,自动生成该数组可能会更容易

Another way to do it that's popular in generated code is:

#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];}

I prefer the switch case way I already mentioned, but depending on how your code is structured it might be easier as part of your build process to generate that array automatically

雪化雨蝶 2024-09-05 13:51:14

你是对的。无法在运行时恢复预处理器定义的标识符(除非您可以阅读源代码,但这是作弊)。您最好创建一个名称常量数组并使用错误代码对其进行索引(当然要进行适当的边界检查) - 编写脚本来生成它应该很容易。

You are correct. There's no way to recover preprocessor-defined identifiers at runtime (unless you can read the source, but that's cheating). You would be better off creating a constant array of the names and indexing it with the error code (with proper boundary checks of course) - it should be quite easy to write a script to generate it.

羁绊已千年 2024-09-05 13:51:14

看一下 boost 预处理器。
您可以创建代码对的列表/数组/序列:

#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

take a look at boost preprocessor.
You could create list/array/sequence of code pairs:

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

#define code1 1
...

// then use preprocessor FOR_EACH to generate error handlers

relevant link:

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

水波映月 2024-09-05 13:51:14

这里的一种可能性是编写一个小程序来解析包含#defines的.h文件,并为convertToString()函数发出相应的源代码。然后,只要 .h 文件发生更改,您就可以让该程序作为构建过程的一部分自动运行。预先需要做更多的工作,但是一旦实现,您将不再需要手动更新 int<->string 转换函数。

如果您想支持使用相同常量的多种语言的代码,这尤其有用。例如,在我的一个应用程序中,更改 #defines 头文件会导致自动重新生成相应的 C++、Python 和 Java 文件。这使得项目维护变得更加容易并且不易出错。

One possibility here is to write a little program that parses the .h file that contains the #defines, and emits the corresponding source code for the convertToString() function. Then you can have that program automatically run as part of your build process whenever the .h file is changed. It's a little more work up front, but once it's implemented you'll never again need to manually update your int<->string conversion function.

This is especially useful if you want to support code in multiple languages that uses the same constants. For example, in one of my applications, changing my #defines header file causes the corresponding C++, Python, and Java files to be auto-regenerated. This makes project maintenance much easier and less error-prone.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文