【问题标题】:Using MACROs to get the 'name' of function parameters使用 MACRO 获取函数参数的“名称”
【发布时间】:2017-09-24 08:26:40
【问题描述】:

我已经实现了一个日志功能,最终在整个代码中都以相同的方式使用它。

void func(int foo, int bar){
    log_api_call("foo", foo, "bar",bar)
    ...
}

所以我决定让它变得更简单,只提取变量名。
所以它会像

log_api_call(foo,bar)

甚至更好

log_api_call()

它会以某种方式扩展到log_api_call("foo", foo, "bar",bar)
我什至不知道从哪里开始“提取”函数变量名。 非常感谢您的帮助。

编辑:
我知道我之前问过的内容超出了 C++ 预处理器的能力,但是 C 宏是否可以将 log_api(a,b) 扩展到 log_api_call("a", a, "b", b) 对于任意数量的参数? 对于定义的数字,这项工作是微不足道的。

谢谢。

【问题讨论】:

    标签: c++ function macros


    【解决方案1】:

    这在标准 C++11(或标准 C11 - 几乎与 C++ 共享其预处理器)中是不可能的。 C or C++ preprocessor 不知道传递给编译器的代码的 AST(因为它在实际解析代码之前运行)。

    我什至不知道从哪里开始“提取”函数变量名。

    请注意,变量和函数 names 仅在编译时(预处理之后)是已知的。所以如果你想要它们,你需要在编译期间工作。在执行时,变量和函数名称通常会丢失(您可以strip 您的可执行文件)。

    您可以生成您的 C++ 代码(例如,使用其他预处理器,如 GPP 或 M4,或编写您自己的东西)。

    您可以自定义您的 C++ 编译器(例如,使用 GCC MELT 中的扩展名或 GCC 插件),例如让log_api_call 调用一些新的内置魔法(编译器内部的处理将完成大部分工作)。这需要几个月的时间,而且是特定于编译器的,我认为这不值得。

    您可以解析 DWARF 调试信息(这也需要几个月的时间,所以我认为这不明智)。

    (我隐约想到的是在Linux系统上编译的C++代码)

    阅读更多关于aspect programming的信息。

    如果您想要如此强大的元编程工具,C++ 是错误的编程语言。阅读更多关于 Common Lisp 强大的宏系统...

    但是对于任意数量的参数,C MACROS 可以将log_api(a,b) 扩展为log_api_call("a", a, "b", b) 吗?对于定义的数字,这项工作是微不足道的。

    没有。你需要一个更强大的预处理器来完成这项工作(或编写你自己的)。对于该特定需求,您可能会考虑自定义您的 source code editor(例如,为 emacs 编写 100 行 ELisp 代码来执行提取和扩展工作在编辑时)。

    PS 实际上,您可以找到一些库(可能是 boost)将参数限制在某个合理的范围内

    【讨论】:

    • 为什么宏不能做到这一点?我的意思是,当然不是针对任何数字,而是针对合理的数字(例如小于 20)您可以使用@Hwalter 的解决方案或类似的解决方案。
    • 在实践中,您确实会将参数限制为例如20,也许你可以找到一些 boost 图书馆这样做
    【解决方案2】:

    我认为从语言内部可以实现的最佳效果是编写一个扩展为 log_api_call("foo", foo, "bar", bar) 的宏 LOG_API_CALL(foo,bar)

    #define LOG_API_CALL(P1,P2) log_api_call(#P1,P1,#P2,P1)
    

    如果您想用一个宏名称支持任意多个参数,这会变得非常棘手,但您也可以为每个参数数量使用一个单独的宏。

    【讨论】:

    • 不错,刚刚编辑以准确询问未定义的参数数量
    • @DsCpp:我有那个代码,但它并不漂亮,而且我目前在我的手机上,所以我不相信自己能正确复制它。
    【解决方案3】:

    这实际上并不太难。

    不过,我建议稍微更改一下规格;而不是:

    log_api(a,b) 扩展为log_api_call("a", a, "b", b)

    ...将NAMED_VALUES(a,b) 之类的内容扩展为"a",a,"b",b 会更有用。然后您可以调用log_api(NAMED_VALUES(a,b)),但您的 log_api 可以保持更通用(例如,log_api(NAMED_VALUES(a,b),"entering function") 是可能的)。这种方法还避免了很多关于零参数情况的复杂情况。

    // A preprocessor argument counter
    #define COUNT(...) COUNT_I(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1,)
    #define COUNT_I(_9,_8,_7,_6,_5,_4,_3,_2,_1,X,...) X
    // Preprocessor paster
    #define GLUE(A,B) GLUE_I(A,B)
    #define GLUE_I(A,B) A##B
    // chained caller
    #define NAMED_VALUES(...) GLUE(NAMED_VALUES_,COUNT(__VA_ARGS__))(__VA_ARGS__)
    // chain
    #define NAMED_VALUES_1(a) #a,a
    #define NAMED_VALUES_2(a,...) #a,a,NAMED_VALUES_1(__VA_ARGS__)
    #define NAMED_VALUES_3(a,...) #a,a,NAMED_VALUES_2(__VA_ARGS__)
    #define NAMED_VALUES_4(a,...) #a,a,NAMED_VALUES_3(__VA_ARGS__)
    #define NAMED_VALUES_5(a,...) #a,a,NAMED_VALUES_4(__VA_ARGS__)
    #define NAMED_VALUES_6(a,...) #a,a,NAMED_VALUES_5(__VA_ARGS__)
    #define NAMED_VALUES_7(a,...) #a,a,NAMED_VALUES_6(__VA_ARGS__)
    #define NAMED_VALUES_8(a,...) #a,a,NAMED_VALUES_7(__VA_ARGS__)
    #define NAMED_VALUES_9(a,...) #a,a,NAMED_VALUES_8(__VA_ARGS__)
    

    这最多支持 9 个参数,但应该很容易看出如何扩展为更多。

    【讨论】:

    • 只是好奇:这也适用于 msvc 吗?他们的预处理器处理可变参数宏参数有点不同。
    • @MikeMB 不,不会。 Here's an approach that will;不幸的是,它不能使用相同的模式(在 MSVC 中像这样“链接”需要 CALL 宏来防止 __VA_ARGS__ 被折叠成单个 arg;但由于它与 CALL blue 一起运行,所以它不能巢)。
    【解决方案4】:

    它会以某种方式扩展到log_api_call("foo", foo, "bar",bar)

    这在标准 C++ 中是不可能的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-13
      • 1970-01-01
      • 1970-01-01
      • 2018-07-27
      • 1970-01-01
      相关资源
      最近更新 更多