为什么gcc会破坏递归扩展宏



你好,

在编译利用递归宏扩展的非常简单的C++程序时,我遇到了一个奇怪的问题:

#define FINAL(a1, a2, a3) const char *p = "final values are: " #a1 " " #a2 " " #a3;
#define SPLIT(a1, a2) a1, a2
#define BRACES(a1, a2) ( a1, a2 )
#define START(macro, a1, a2) macro BRACES(a1, SPLIT a2)
START(FINAL, 1, (2, 3))
int main(int argc, char* argv[])
{
std::cout << p << std::endl;
return 0;
}

该程序期望打印"最终值为:1 2 3"文本。它是在Visual Studio 2008上实现的。

但当我试图在Windows7上使用mingw32 gcc-6.3和在Linux Ubuntu-16:上使用gcc-5.4编译它时,我看到了一个问题

$ g++ -I /e/dev-libs/boost/boost_1_60_0-mingw32/include/boost-1_60 test.cpp
test.cpp:7:24: error: expected constructor, destructor, or type conversion before '(' token
#define BRACES(a1, a2) ( a1, a2 )
^
test.cpp:8:36: note: in expansion of macro 'BRACES'
#define START(macro, a1, a2) macro BRACES(a1, SPLIT a2)
^~~~~~
test.cpp:12:1: note: in expansion of macro 'START'
START(FINAL, 1, (2, 3))
^~~~~

看起来它不依赖于C++标准,我已经用gcc尝试了-std=C++11和-std=C++03。我已经重读了几遍C++11标准的第16.3部分"宏替换",但我想我错过了一些重要的东西。

这里的代码可能有什么问题?

还有一件更重要的事情:BOOST预处理器库中的BOOST _PP_SEQ_FOR_EACH_I_R也无法编译,这非常奇怪:

#include <iostream>
#include <boost/preprocessor/seq/for_each_i.hpp>
#define FINAL2(r, data, id, value) const char *p ## id = #value;
BOOST_PP_SEQ_FOR_EACH_I_R(_, FINAL2, _, (a)(b)(c))
int main()
{
std::cout << p1 << std::endl;
return 0;
}

错误输出:

$ g++ -I /e/dev-libs/boost/boost_1_60_0-mingw32/include/boost-1_60 -std=c++03 test2.cpp
In file included from test2.cpp:2:0:
E:/dev-libs/boost/boost_1_60_0-mingw32/include/boost-1_60/boost/preprocessor/seq/for_each_i.hpp:96:96: error: expected constructor, destructor, or type conversion before '(' token
#    define BOOST_PP_SEQ_FOR_EACH_I_R_DETAIL_CHECK_EXEC(r, macro, data, seq) BOOST_PP_FOR_ ## r((macro, data, seq, 0, BOOST_PP_SEQ_SIZE(seq)), BOOST_PP_SEQ_FOR_EACH_I_P, BOOST_PP_SEQ_FOR_EACH_I_O, BOOST_PP_SEQ_FOR_EACH_I_M)
                          ^
E:/dev-libs/boost/boost_1_60_0-mingw32/include/boost-1_60/boost/preprocessor/control/iif.hpp:32:31: note: in expansion of macro 'BOOST_PP_SEQ_FOR_EACH_I_R_DETAIL_CHECK_EXEC'
# define BOOST_PP_IIF_1(t, f) t
^
E:/dev-libs/boost/boost_1_60_0-mingw32/include/boost-1_60/boost/preprocessor/control/iif.hpp:25:39: note: in expansion of macro 'BOOST_PP_IIF_1'
#    define BOOST_PP_IIF_I(bit, t, f) BOOST_PP_IIF_ ## bit(t, f)
^~~~~~~~~~~~~

第一个例子的问题似乎很简单。第一次扩展后,预处理将停止执行您希望它执行的操作:

START(FINAL, 1, (2, 3))
// becomes
FINAL BRACES(1, SPLIT (2, 3))

FINAL没有参数,所以生成的代码会被打乱。

正如@VTT所说,START(FINAL, 1, (2, 3))的替代品是FINAL BRACES(1, SPLIT(2, 3));然后重新扫描该文本,但它不具有FINAL的宏扩展形式。

您可以通过另一个级别的间接(或者更准确地说是宏扩展)来实现您想要的效果:

#define SPLIT(a1, a2) a1, a2
#define BRACES(a1, a2) ( a1, a2 )
#define APPLY(a1, a2) a1 a2
#define START(macro, a1, a2) APPLY(macro, BRACES(a1, SPLIT a2))

使用标准指定的规则,您的宏:

START(FINAL, 1, (2, 3))

参数替换期间扩展为:

FINAL BRACES(1, SPLIT (2, 3))

然后在重新扫描和进一步替换期间重新扫描;在这个步骤中,可以看到FINAL,但它后面没有参数。由于您只有一个类似宏的函数,所以不会发生任何事情。因此CPP继续前进。然而,BRACES有两个参数,并且是一个类似于宏的两个参数函数,因此它进行了扩展(SPLIT的扩展是此扩展的一部分),剩下的是:

FINAL (1, 2, 3)

现在CPP是用BRACES完成的,所以它进一步移动。没有备份和评估/扩展FINAL的步骤。如果你想实现这一点,你需要一个间接的宏(比如@rici向你展示的那个)。

微软的CPP扩展了这一点并不奇怪;MS的CPP是非标准的。

至于第二个问题(实际上应该是一个单独的问题):

BOOST_PP_SEQ_FOR_EACH_I_R(_, FINAL2, _, (a)(b)(c))

那个电话是错的。_R变体宏需要一个与宏集相对应的r参数(这是第一个参数)。这些是数字;它们不可能只是CCD_ 13。CCD_ 14将起作用;但是,除非您正在执行递归/复杂的操作,否则实际上不需要调用_R版本。

最新更新