在BOOST_PP_SEQ_FOR_EACH中调用含__VA_ARGS__的宏时出现额外空参数问题
我之前也碰到过几乎一模一样的情况,问题核心在于Boost.Preprocessor的BOOST_PP_SEQ_FOR_EACH对宏参数的要求和你的DO_STUFF宏的参数格式不匹配,导致可变参数解析时出现异常。
先还原你的场景:
你的核心宏定义是这样的:
#define DO_STUFF(name,...) \ STUFF1(name,__VA_ARGS__) \ STUFF2(name,__VA_ARGS__) \ STUFF3(name,__VA_ARGS__)
单独调用DO_STUFF(dave, int, char)时,预处理器能正确把__VA_ARGS__解析为int, char,生成符合预期的代码。但用BOOST_PP_SEQ_FOR_EACH遍历序列时,__VA_ARGS__会莫名多出空参数,这是因为你忽略了BOOST_PP_SEQ_FOR_EACH的宏调用规则。
问题根源
BOOST_PP_SEQ_FOR_EACH要求传入的宏必须接受三个固定参数:
- 递归状态参数(通常用
r表示,一般用不到) - 用户自定义的额外数据参数(比如你写的
_) - 序列中的单个元素(比如
(dave, int, char)这样的元组)
如果直接把DO_STUFF传给BOOST_PP_SEQ_FOR_EACH,它会强行把这三个参数塞给DO_STUFF,导致name被赋值为递归状态参数,__VA_ARGS__被填充为额外数据+序列元素,完全偏离了你原本的参数设计,自然会出现多余的空参数或者错误参数。
解决方案
给DO_STUFF写一个适配BOOST_PP_SEQ_FOR_EACH的包装宏,把序列元素拆分成你需要的name和可变参数,再转发给DO_STUFF:
步骤1:编写包装宏
#include <boost/preprocessor/tuple/elem.hpp> #include <boost/preprocessor/tuple/rest.hpp> // 适配BOOST_PP_SEQ_FOR_EACH的包装宏 #define DO_STUFF_WRAPPER(r, unused_data, elem) \ DO_STUFF( \ BOOST_PP_TUPLE_ELEM(0, elem), // 取出元组的第一个元素作为name BOOST_PP_TUPLE_REST(0, elem) // 取出元组剩下的元素作为__VA_ARGS__ )
这里用BOOST_PP_TUPLE_ELEM提取元组的第一个元素作为name,用BOOST_PP_TUPLE_REST提取剩余元素作为可变参数,完美匹配DO_STUFF的参数格式。
步骤2:定义你的序列
序列里的每个元素必须是元组格式(用双层括号包裹):
#define MY_STUFF_SEQ \ ((dave, int, char)) \ ((alice, float)) \ ((bob)) // 这个元素没有额外参数,测试空可变参数场景
步骤3:调用SEQ_FOR_EACH
BOOST_PP_SEQ_FOR_EACH(DO_STUFF_WRAPPER, _, MY_STUFF_SEQ)
可选优化:处理空可变参数的逗号问题
如果你的STUFF1/2/3宏不接受末尾多余的逗号(比如当__VA_ARGS__为空时,会生成STUFF1(bob,)这样的错误代码),可以给DO_STUFF加上##连接符来消除多余逗号:
#define DO_STUFF(name,...) \ STUFF1(name,##__VA_ARGS__) \ STUFF2(name,##__VA_ARGS__) \ STUFF3(name,##__VA_ARGS__)
##是预处理器的连接符,当__VA_ARGS__为空时,会自动删除前面的逗号,避免语法错误。
这样修改后,不管是单独调用DO_STUFF,还是用BOOST_PP_SEQ_FOR_EACH批量调用,__VA_ARGS__都会被正确解析,不会出现额外的空参数了。
内容的提问来源于stack exchange,提问作者Claire Andrews

