【问题标题】:Functional-Programming Style in C++ Macros: Is this documented anywhere?C++ 宏中的函数式编程风格:这在任何地方都有记录吗?
【发布时间】:2013-10-30 02:16:41
【问题描述】:

阅读一些 C++ 代码时,我遇到了我称之为函数宏的“函数式”使用,大致如下(这是一个完全程式化的例子来说明这一点):

#define TOP_LEVEL(ARG1)     \
ARG1("foo1","bar1")     \
ARG1("foo2","bar2")

#define NEXT_LEVEL(ARG2A, ARG2B)    \
cout << ARG2A << " and " << ARG2B;

TOP_LEVEL(NEXT_LEVEL)

我对这门语言比较陌生,起初我无法弄清楚,但后来我只通过预处理器 (g++ -E) 运行它,结果发现它解析为:

cout << "foo1" << " and " << "bar1"; cout << "foo2" << " and " << "bar2";

你看到它在那里做了什么吗?它将宏 NEXT_LEVEL 像函数指针传递给宏 TOP_LEVEL。看到这可能有多么有用,我想了解更多信息:将函数传递给其他函数是pretty sophisticated stuff,并且必须至少有更多关于该技术的内容要说。

尽管在谷歌上搜索了很多,但我找不到任何证据表明预处理器的这个功能甚至存在,更不用说任何接近文档的东西了:herehereherehere 只是四个例子跳过此内容的宏教程;最后一个甚至有一个名为“高级宏技巧”的部分 - 这肯定符合条件!?

(请注意,这与简单地以另一个 求值 函数宏作为参数调用函数宏完全不同 - FOO(BAR(2)) 更直接。)

我的问题是:

  • 此行为是否有实际名称?
  • 它是否记录在任何地方?
  • 是常用的,还是有众所周知的陷阱等?

【问题讨论】:

  • 该术语是一个 X 宏。一般用多一点,比如说,gusto;一个宏是在包含一个头文件之前定义的,该头文件期望该宏可以按照自己的意愿处理数据。
  • 哇 - 感谢您的快速回答。我很高兴在 X-Macros 的 Wikipedia 页面上读到,尽管它们自 1960 年代就已经存在,但它们是“relatively unkown”——所以至少不仅仅是我 ;-)
  • 是的。 :) 我会让别人写一个关于它的好答案,但如果没有人解决它,我会的。
  • 关于宏语言的这一特性的文献并不多,我并不感到惊讶,一般来说,宏被认为是利大于弊。我已经使用该技巧进行了几次自动代码生成(定义一个FOR_EACH_ENUMERATOR,它接受一个宏,然后使用两个参数(enumerator,value) 应用它。然后使用该宏在枚举和转换函数中创建值从/到用于日志记录/io 的字符串...我曾尝试使用该宏在某一时刻提供反射,但除了那个程序之外,我以后没有使用它...
  • 是的,Boost.PP 有一些很好的例子。

标签: c++ c-preprocessor x-macros


【解决方案1】:

这个想法被创造了“X-Macro”。一些定义不包括您的特定示例(X-宏通常涉及更多,包括一个文件),但任何相关信息。搜索时,关于这将属于该术语。

正如 chris 在 cmets 中提到的,Boost.Preprocessor 使用这个想法效果很好。流行的用途是:BOOST_PP_REPEATBOOST_PP_LIST_FOR_EACH,最强大的是:BOOST_PP_ITERATE

BOOST_PP_ITERATE 是一个“真正的”X 宏;包含单个文件会扩展为依赖于之前定义的宏的内容。我在这个other answer 中展示了一个更“正确”的骨架框架,但一个例子是:

// in xyz_data.def
DEFINE_XYZ(foo, 1, "Description A")
DEFINE_XYZ(bar, 5, "Description B")
DEFINE_XYZ(baz, 7, "Description C")

然后当我只想要第 1 列时,我可以这样做:

#define DEFINE_XYZ(name, number, desc) some_func(name)
#include "xyz_data.def"

在其他地方我想为每个函数生成一些函数,我可以这样做:

#define DEFINE_XYZ(name, number, desc)                              \
    int BOOST_PP_CAT(get_number_for_, name)()                       \
    {                                                               \
       std::clog << "Getting number, which is: " desc << std::endl; \
                                                                    \
       return number;                                               \
    }

#include "xyz_data.def"

然后您可以生成一个枚举,其中名称等于数字等。

强大之处在于,当我想添加一个新的 xyz 时,我只需将它添加到一个位置,它就会神奇地出现在它需要的任何地方。我在一个非常大的代码库中做了类似的事情,以将一些书签数据保存在一个中心位置,但是各种属性在不同位置的使用方式不同。

请注意,通常没有办法解决这个问题;我所拥有的是语法不同,所以没有其他语言功能可以将它推广到那个级别,只有宏。宏并不邪恶。

您所拥有的实际上是一个 X 宏,其中 .def 文件是自包含的,足以成为 #define。换句话说,#include "xyz_data.def" 就是 TOP_LEVEL

这只有一个很大的缺点,具有讽刺意味的是,它不是使用 X 宏本身,而是它们对 C 和 C++ 编译器的影响。问题是预处理器允许我们在每次包含文件时更改其预处理结果,即使文件内容完全相同

您可能听说过,与现代语言相比,C 和 C++ 的编译速度很慢,这就是原因之一。它没有适当的模块/打包系统,只是临时包含其他文件。我们刚刚了解到,一般来说这是无法避免的。哎呀。 (也就是说,编译器很聪明,例如,当您在文件周围包含保护时会注意到,并避免多次处理它。但这是视情况而定。)

也就是说,使用 X-Macros 本身不应该对实际程序的编译时间造成很大的影响。只是它们的潜在存在延伸到了真实的词中,并与编译器的头脑一致。

【讨论】:

  • 过于依赖提升来克服我的热情的所有陷阱(不是这些在那里没有得到妥善解决)。如果需要,我仍然更喜欢使用模板元编程和可变参数模板。
  • @g-makulik:你真的想不出一个问题,宏是正确的解决方案吗?你住的小盒子。
  • 没有进入赞成/反对宏观辩论(我对语言太陌生)我会说这是对我的问题的全面而翔实的答案:谢谢。
【解决方案2】:

这里有几个讲座:C is purely functionnal

我建议你看看libppmacrofun

【讨论】:

    猜你喜欢
    • 2010-11-08
    • 1970-01-01
    • 2022-11-30
    • 2022-07-11
    • 1970-01-01
    • 2016-08-17
    • 1970-01-01
    • 2013-03-28
    • 2013-06-22
    相关资源
    最近更新 更多