衔引 原理毕,复用续。历观编程概念,避及重复,提其不变,于迭代与递归为甚。产生式元编程,乃欲自动产生百千代码,迭代递归,自是重要组件,不可不备。 本章以 FOR_EACH 为例,渐次派生问题,引出技术要点,实现强大组件。 迭代 FOR_EACH 用来迭代数据集合,依次取出数据,后调用函数处理。参数易定,一个回调函数加上可变参数。代码表示: #define FOR_EACH(call, …) // Invoke FOR_EACH(Foo, 1, 2, 3) 实现需逐个取出可变宏参数,调用 Foo() 处理。上章已讲解重载之实现法,此乃条件逻辑在宏中的表示法。无循环时,递归思想便是拆解参数包的唯一方式,将重载实现法与递归思想结合起来,即可达逐个取参数之效。 遂可如此表示: #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, …)… Continue Reading 《产生式元编程》第二章 自复用代码生成技

引言 自 C 以来,宏为代码生成工具。至 C++98 则有模板,泛型编程日益盛行。迄于 C++20,引编译期表达式,添 Concepts,元编程基础支持始渐完善。由此元编程之技稍简。而静态反射乃元编程系统之核心,却迟久未至,产生式元编程遂仍繁复。 所述元编程之书文,指不胜屈,其间也以编译期计算为主,奇技淫巧,小大靡遗。而于产生式元编程,言者寥寥,常见于库中直用。于是有此系列,略述浅见,供同道者读阅之。 产生式元编程,即为编译期代码生成的技术,各类系统,特性不侔,用法与能力亦有所殊。是以本系列跨度稍大,然只涉标准,不论自成之法,预计十章左右,从旧有到未来之特性,俱可囊括。 问题 代码生成以宏为先,虽是旧工具,然方今模板元编程的能力尚有不足,在产生式元编程的核心特性「源码注入」进入标准之前,它仍是一种常用的代码生成工具。 宏编程得从可变宏参数开始谈起,可变模板参数的个数可以通过 sizeof…(args) 获取,宏里面如何操作呢?便是本章的问题。 接着便逐步分析问题,实现需求,其间穿插宏编程之原理。 分析与实现 宏只有替换这一个功能,所谓的复杂代码生成功能,都是基于这一核心理念演绎出来的。故小步慢走,循序渐进,便可降低理解难度。 问题是获取宏参数包的个数,第一步应该规范过程。 过程的输入是一系列对象,对象类型和个数是变化的;过程的输出是一个值,值应该等于输入对象的个数。如果将输入替换为任何类型的其他对象,只要个数没变,结果也应保持不变。 于是通过宏函数表示出过程原型: #define COUNT_VARARGS(…) N 根据宏的唯一功能可知,输出只能是一个值。依此便可否定遍历迭代之类的常规方式,可以考虑多加一层宏,通过由特殊到普遍的思想来不断分析,推出最终结果。 #define GET_VARARGS(a) 1 #define COUNT_VARARGS(…) GET_VARARGS(__VA_ARGS__) 目前这种实现只能支持一个参数的个数识别,可通过假设,在特殊的基础上逐次增加参数。于是得到: #define GET_VARARGS(a, b) 2 #define GET_VARARGS(a) 1 #define COUNT_VARARGS(…) GET_VARARGS(__VA_ARGS__) 若假设成立,通过暴力法已能够将特殊推到普遍,问题遂即解决。但是宏并不支持重载,有没有可能实现呢?通过再封装一层,消除名称重复。得到: #define GET_VARARGS_2(a, b) 2 #define GET_VARARGS_1(a) 1… Continue Reading 《产生式元编程》第一章 宏编程计数引原理

宏编程的基本原理都已暗含于解决前两节问题的过程当中,本节开始,依此继续展开。 经由前面的努力,我们已掌握条件逻辑在宏编程的表示,代码生成往往涉及循环,所以今天来讲如何实现一个 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,那么就从… Continue Reading T230925 Generative Metaprogramming with Macro Preprocessor (Part 3)

经过上篇分析实现,第一个需求「计算可变宏参数个数」已由 COUNT_VARARGS 基本实现。 让我们先总结一下用到的思想和发现的技术,再进入下一步。 2.1 通过增加一个间接层,能够解决无法直接解决的问题。 2.2 小步快走,由特殊逐渐扩展到普遍,能够降低问题的解决难度。 2.3 规范过程,确认变与不变,逐步控制变量,能够全面分析问题。 2.4 尝试改变输入的顺序、大小、个数…… 也许能有新发现。 2.5 初步发现规律时,扩大样本验证,能够将特殊推到普遍。 2.6 可变宏参数作为输入和欲输出结果组合起来,其间规律可以表达条件逻辑。 2.7 扩展编译器及语言版本,能够更全面地测试解决方案的普遍性。 那么本篇就来进一步完善解决方案,使它支持 C++20 以下版本。 问题的关键在于 ##__VA_ARGS__ 不具备通用性,此时一种精妙的技术是分情况讨论,即零参和多参分别处理。考虑上节分析过程中的一条失败道路: #define GET_VARARGS_2(a, b) 2 #define GET_VARARGS_1(a) 1 #define GET_VARARGS(…) GET_VARARGS_X(__VA_ARGS__) #define COUNT_VARARGS(…) GET_VARARGS(__VA_ARGS__) 这是函数重载的思路,由于必须确定 GET_VARARGS_X 中的 X,而 X 又和可变宏参数相关,可变宏参数又属于无限集,因而无法确定这个 X,导致此路不通。但是,如果改变前提条件,使 X 的值属于有限集,这条路便可走通,这里便能够利用起来。 只考虑零参和多参的情况,X 的取值范围就可以定在 0… Continue Reading T230920 Generative Metaprogramming with Macro Preprocessor (Part 2)

这期开始将写一些关于宏编程的内容,讲解宏作为代码生成工具在产生式元编程中的运用。 有人可能会有疑问,已有模板作为元编程工具,为何还需要使用宏这种古老的代码生成工具?自然是因为如今模板元编程的代码生成能力仍有不足,在第三阶段 C++ 元编程的核心工具「源码生成」进入标准之前,宏依旧是一种常用的代码生成工具。 文章定位是 TGS,所以每 Part 只会讲一个单独的技术点,利于循序渐进地吸收。 宏编程得从 Variadic Macros 开始谈起,可变模板参数的个数可以通过 sizeof…(args) 获取,宏里面如何操作呢?便是本篇的主题。 首先需要明确,宏只有替换这一个功能,所谓的复杂代码生成功能,都是基于这一核心理念演绎出来的。因此,只要小步慢走,层层递进,复杂能力其实也并不复杂。 我们的需求是获取宏参数包的个数,第一步应该规范过程。 过程的输入是一系列对象,对象类型和个数是变化的;过程的输出是一个值,值应该等于输入对象的个数。如果将输入替换为任何类型的其他对象,只要个数没变,它的结果应该保持不变。 于是通过宏函数表示出过程原型: #define COUNT_VARARGS(…) N 根据宏的唯一功能可知,输出只能是一个值。依此便可否定遍历迭代之类的常规方式,可以考虑多加一层宏,通过由特殊到普遍的思想来不断分析,推出最终结果。 #define GET_VARARGS(a) 1 #define COUNT_VARARGS(…) GET_VARARGS(__VA_ARGS__) 目前这种实现只能支持一个参数的个数识别,我们通过假设,在特殊的基础上逐渐增加更多参数。于是得到: #define GET_VARARGS(a, b) 2 #define GET_VARARGS(a) 1 #define COUNT_VARARGS(…) GET_VARARGS(__VA_ARGS__) 如果该假设成立,通过暴力法已能够将特殊推到普遍,问题也就解决了。但是,宏并不支持重载。有没有可能实现呢?通过再封装一层,消除名称重复。 #define GET_VARARGS_2(a, b) 2 #define GET_VARARGS_1(a) 1 #define GET_VARARGS(…) GET_VARARGS_X(__VA_ARGS__)… Continue Reading T230917 Generative Metaprogramming with Macro Preprocessor (Part 1)