宏编程的基本原理都已暗含于解决前两节问题的过程当中,本节开始,依此继续展开。

经由前面的努力,我们已掌握条件逻辑在宏编程的表示,代码生成往往涉及循环,所以今天来讲如何实现一个 FOR_EACH

FOR_EACH 用来迭代数据集合,依次取出数据,然后调用函数处理。因此,参数很容易确定,一个回调函数加上可变参数。代码表示:

#define FOR_EACH(call, ...)

至于展开参数,可以利用重载技术加上递归思想,因为重载技术在前文已经有相关组件,所以实现起来非常简单。

#define _FOR_EACH_1(call, x) call(x)
#define _FOR_EACH_2(call, x, ...) call(x) _FOR_EACH_1(call, __VA_ARGS__)
#define _FOR_EACH_3(call, x, ...) call(x) _FOR_EACH_2(call, __VA_ARGS__)
#define _FOR_EACH_4(call, x, ...) call(x) _FOR_EACH_3(call, __VA_ARGS__)
#define _FOR_EACH_5(call, x, ...) call(x) _FOR_EACH_4(call, __VA_ARGS__)

#define FOR_EACH(call, ...) \
    OVERLOAD_HELPER(_FOR_EACH_, COUNT_VARARGS(__VA_ARGS__))(call, __VA_ARGS__)

通过层层递归,便可以把大问题拆分成一个个小问题。如果数据集合大小为 5,那么就从 5 开始,处理一个再调用相同的逻辑进行处理。宏不支持真正的重载,其实是通过多个名称不同的宏函数来模拟重载效果,递归同样如此,但是整体的思想是不变的。

现在便可以通过它来生成代码,例如:

void foo(int x) {
    printf("x: %d\n", x);
}

#define Foo(x) foo(x);
FOR_EACH(Foo, 1, 2, 3)

/**
 * Output:
 * x: 1
 * x: 2
 * x: 3
*/

可以看到,依旧是借助了一个中间层 Foo 来完成调用,因为重载实现中的 call(x) 后面并没有 ;,以这种中间件来延迟代码生成。为什么可以延迟生成呢?传递之时名称为 Foo,而非 Foo(arg),故只是一个普通的名称,直到将 call(x) 替换为 Foo(x) 时,才真正开始代码生成。

那么对于 0 个参数呢?这里并不是问题,因为我们并不会实际使用该参数,它也不会影响结果,只需如此处理即可:

#define _FOR_EACH_0(call, ...)

进一步,让代码强制展开,以支持 MSVC。最终实现为:

#define _FOR_EACH_0(call, ...)
#define _FOR_EACH_1(call, x) call(x)
#define _FOR_EACH_2(call, x, ...) call(x) _FOR_EACH_1(call, __VA_ARGS__)
#define _FOR_EACH_3(call, x, ...) call(x) _FOR_EACH_2(call, __VA_ARGS__)
#define _FOR_EACH_4(call, x, ...) call(x) _FOR_EACH_3(call, __VA_ARGS__)
#define _FOR_EACH_5(call, x, ...) call(x) _FOR_EACH_4(call, __VA_ARGS__)

#define FOR_EACH(call, ...) \
    EXPAND( OVERLOAD_HELPER(_FOR_EACH_, COUNT_VARARGS(__VA_ARGS__))(call, __VA_ARGS__) )

这个工具很有用,可以批量生成许多代码。

比如批量生成类:

#define DECLARE_CLASS(cls) class cls{};
FOR_EACH(DECLARE_CLASS, Bar, Duck)

Duck duck;

批量生成变量:

#define DECLARE_VARIABLES(var) int variable_ ## var;
FOR_EACH(DECLARE_VARIABLES, 1, 2, 3, 4, 5)

variable_1 = 1;
variable_2 = 2;
variable_3 = 3;
variable_4 = 4;
variable_5 = 5;

更多内容,请见下节分享。

Leave a Reply

Your email address will not be published. Required fields are marked *

You can use the Markdown in the comment form.