( copy from http://banjuan.net/wiki/trick/c-macro, 欢迎过去围观)
目标
这里,我的目标是使用宏进行代码生成。例如以下宏:
ATTR(id, name, value, price)
需要展开为一组函数调用:
read(“id”, item->id);
read(“name”, item->name);
read(“value”, item->value);
read(“price”, item->price);
而上面的宏是在一个更大的宏中使用:
LOAD_ITEM(MyLoader, \
ATTR(id, name, value, price), \
GROUP(prize, p_id, p_value) )
这个大宏需要展开为一个函数体:
int MyLoader::load_item()
{
read(“id”, item->id);
read(“name”, item->name);
read(“value”, item->value);
read(“price”, item->price);
while( m_xml.findChildElem(“prize”) )
{
struct prize* sub_item = new struct prize;
read_sub(“p_id”, sub_item->p_id);
read_sub(“p_value”, sub_item->p_value);
item->prize.push_back(sub_item);
}
return 0;
}
ATTR(id, name, value, price) 的实现
先说一组函数如何生存吧。这个其实就是个for/while循环,或者理解为递归也行。
可是GCC不支持宏的循环/递归处理。宏的复杂应用一般也就是不定项参数了(通常 是封装printf之类的),可是那也就是将参数直接传递罢了。
从我查阅到的资料来看,所有的递归宏处理都是转化为了【穷举】进行实现的。
穷举 =? 递归
由于宏ATTR()的参数是任意个属性,可以预先设定好参数的最大个数,然后人肉 递归,穷举出所有的展开结果:
#define ATTR_(attr) read(#attr, item->attr)
#define ATTR_0(attr, args…)
#define ATTR_1(attr, args…) ATTR_(attr); ATTR_0(args)
#define ATTR_2(attr, args…) ATTR_(attr); ATTR_1(args)
#define ATTR_3(attr, args…) ATTR_(attr); ATTR_2(args)
#define ATTR_4(attr, args…) ATTR_(attr); ATTR_3(args)
#define ATTR_5(attr, args…) ATTR_(attr); ATTR_4(args)
那么,如果有四个参数,就可以直接调用ATTR_4()这个宏来处理四参数的展开:
ATTR_4(id, name, value, price)
=> ATTR_(id); ATTR_3(name, value, price)
=> ATTR_(id); ATTR_(name); ATTR_2(value, price)
=> ATTR_(id); ATTR_(name); ATTR_(value); ATTR_1(price)
=> ATTR_(id); ATTR_(name); ATTR_(value); ATTR_(price); ATTR_0()
=> ATTR_(id); ATTR_(name); ATTR_(value); ATTR_(price);
可是,如果我有五个参数,咋办???只有乖乖调用ATTR_5(….)这个宏。 这种人工计算参数个数的做法实在是土。有没有办法自动计算参数的个数??
GCC的预处理器并没有提供参数个数的变量,而且貌似网上也很少有人提到需要这个值。 不过,今天查看维基百科上的资料时1),意外的发现 有人实现了一个宏 2) ,用来计算参数的个数:
#define PP_NARG(…) \
PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(…) \
PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,…) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
真是精巧呀。使用 PP_NARG() 宏,我们就可以自动将 ATTR() 转换为对应的 带参数个数的穷举版宏了。
#define CONNECT(a, b) CONNECT1(a, b)
#define CONNECT1(a, b) CONNECT2(a, b)
#define CONNECT2(a, b) a##b
#define EXPAND_NARG(name, args…) \
CONNECT(name, PP_NARG(args))(args)
#define ATTR(args…) EXPAND_NARG(ATTR_, args)
以上宏定义整理在文件macro.h中了,读者可以下载回来使用 gcc -E 展开宏, 尝试一番。
LOAD_ITEM(loader, code1, code2, …)
使用上面的宏,已经可以搞定 ATTR() 和 GROUP() 了。至于外面的大宏, 实际上是先展开为一下的代码:
LOAD_ITEM_FUNC(MyLoader)
{
ATTR(id, name, value, price);
GROUP(prize, p_id, p_value);
LOAD_ITEM_END();
}
上面的代码再进一步展开为实际的函数体。
对于这里的展开,我暂时还没发现什么巧妙的做法,唯有使用上述的【穷举法】。 具体就没啥技术含量了,也不再多解释了。
参考
* Roland Illig, Re: __VA_NARG__, http://newsgroups.derkeiler.com/Archive/Comp/comp.std.c/2006-01/msg00072.html
1) 一时找不到来源了,明天补上
2) 这就是今天最大的收获啦~~