本篇源于群友的某个提问,需求略显特别,却是一个非常不错的产生式元编程例子,遂记录一下。 原始问题 优化后的原始问题,代码如下: struct base { virtual void foo() = 0; }; template<int I, int J, int K, int L> struct derived : public base { void foo() override final { std::cout << "derived<" << I << ',' << J << ',' << K << ',' << L << ">::foo()\n";… Continue Reading Clean Conversion from Run-time Values to Compile-time Constants in C++

Introduction 模板是第一阶段元编程最核心的工具,中篇以两章五星难度的内容开头,深入纵览其核心技术与诸般妙诀。本章要讨论的元编程工具——Fold Expressions,依旧处于第一阶段,是 C++17 引入的一个跨越性产生式特性。 该特性从更高层面抽象了参数包拆解方式,消除了传统递归所带来的复杂性,是非常有用的编译期遍历方式。因此,这个特性是产生式元编程中的关键部分,这也是单独分配一章深度讨论的原因。 这个特性也并不完善,C++26/29 依旧会增加与其相关的特性,增加哪些?为什么加?痛点在哪儿?这些问题也将在本章给出答案。 Fold Fold 这个概念来自函数式编程,本身指的是一类高阶函数,这些函数使用给定的组合操作来分析递归数据结构,并通过递归处理其组成部分的结果来重新组合,最终构建出一个返回值。简而言之,Fold 能够递归地遍历数据结构中的每个元素,并通过一个组合函数将这些元素的值合并为一个结果。这个组合函数通常定义了如何将两个值合并在一起,以及如何处理基础情况(如空数据结构)。 这种从递归数据结构中提取信息的数学概念称为 Catamorphism(源自古希腊语:κατά "向下" 和 μορφή "形式,形状"),表示从一个初始代数到其他代数的唯一同态映射。编程中,目标代数通常就是一个单一的值或结果。若从词的本意来理解,其实指的就是将一复杂数据结构向下拆解成简单数据结构的过程,这个过程递归地利用一个组合函数来完成。Catamorphism 通过抽象化递归数据结构的处理方式,提供了一种通用的模式来遍历和处理递归数据结构,使得代码更具有可读性和可维护性,适用于各种场景,如求和、乘积、查找、过滤等。 Fold 只是 Catamorphism 的一个具体实现,主要是指对列表和序列的处理。例如,对列表 [1, 2, 3, 4] 求和,可以这样表示: foldl (+) 0 [1, 2, 3, 4] = (((0 + 1) + 2) + 3) + 4 = 10 foldr (+) 0… Continue Reading 《产生式元编程》第七章 巧活用折叠表达式

前言 模板编程,技巧如云,妙艺似雨。第五章滔滔滚滚地讲述了模板的核心概念和常用技术,篇幅稍长,诸般妙诀,未遑悉录,放于本章。 闲话不题,本章尽是一些巧妙的实践,于极尽模板产生式编程大有用处,难度不低,重要重要! 本章代码非是示例,而是 GMP 库的真实代码。 Type List Type List 是个只装类型的容器,不含任何数据、函数或类型成员,定义相当简单。代码如下: template<typename…> struct type_list {}; 可变模板参数是实现的核心,表示 Type List 中存入的类型。类型并无一致性要求,是以 Type List 本身还是个异构容器。为何需要这种容器?只因编译期可能需要对很多类型操作,借助这种容器,能够方便地保存各种各样的类型。 可是就一行代码,也没见能进行什么操作呀?其实,Type List 只是个类型容器,需要配套算法才能发挥作用,这些算法才是关键。算法可以分成如下几类: 容积(Capacity) 大小(type_list_size_v) 判空(type_list_empty) 元素访问(Element access) 索引访问(type_list_element_t) 前向类型(type_list_front) 后向类型(type_list_back) 尾部类型(type_list_tail) 内含检测(type_list_contains_v) 元素操作(Element manipulation) 合并(type_list_concat_t) 移除(type_list_remove_t) 前向移除(type_list_pop_front) 后向移除(type_list_pop_back) 插入(type_list_insert_t) 前向插入(type_list_push_front) 后向插入(type_list_push_back) 反转(type_list_reverse_t) 去重(type_list_unique_t) 过滤(type_list_filter_t) 搭配以上这些基础算法,Type List 方得显现妙用。以下各个小节,便来展示各个算法的实现,这些实现并不一定是最优的,但却能展示模板技巧。GMP… Continue Reading 《产生式元编程》第六章 感今朝妙艺几人知

前言 本系列分为上中下三个篇章,前四章作为上篇,详细介绍了宏在产生式编程中的原理和应用,本章开始步入中篇,正式进入 C++ 的产生式元编程技术。 宏是 C 时期的产物,功能颇为简陋,亦非图灵完备,即或诸般奇技妙诀加身,限制仍多。且调试不易,有所束缚,以是仅作辅助,用来实现简单的代码生成功能。至 C++ 时期,产生式元编程工具陡增,其中以模板为一切的根基,发枝散叶,尔今正向静态反射前进。 本章涉及不计其数的模板技术,主要集中于模板的核心理论和用法,概念错综复杂,技术层出不穷。难度等级绝对不低,是本系列中非常重要的一章。 纵然某些概念和技术你已知悉,也莫要跳过某个小节,因为本系列的重心不在模板编程,而在产生式编程,论述角度将有所差异。 泛型……元编程 传统过程式编程的思路是将逻辑作为函数,数据作为函数的输入和输出,通过无数个函数来组织完整的逻辑需求,好似搭积木,构建出各种物体。 面向对象编程更进一步,思路是将逻辑和数据合并起来,构成一个个类,类中包含各种数据和函数,外部只能借助公开函数操纵这些数据。这种以对象表示现实事物的方式与人的思维更加接近,简化了抽象化的难度。 泛型编程则欲将逻辑和数据分离,并提供一种抽象化数据的方式,使得不同的数据能够使用同一种逻辑,极大降低了代码的重复性。于是,同一个函数能够传递不同的参数类型,这叫函数模板;同一个类能够传递不同的数据,这叫类模板。 C++ 是一种多范式语言,以模板支持泛型编程,允许编写泛型的函数和数据,功能不依赖于某种具体的数据类型,使用 SFINAE 和 Concepts 约束抽象化的类型。这种能力使其支持 Parametric Polymorphism,在多态的选择上不必局限于传统的 Subtype Polymorphism,可以使用编译期多态替代运行期多态。 C++ 中,元编程指的是发生于编译期的编程,模板为 C++ 带来泛型编程的同时,也带来了元编程能力。由模板实现的元编程,称为模板元编程。而产生式元编程,指的是发生于编译期的对于编程的编程,模板本就支持在编译期生成代码,因此模板也能够实现产生式元编程。 抽象化、具体化……模板 在模板世界中,函数不再是具体的函数,数据类型也不再是具体的数据类型。模板宛如一个模具,借其能够产生各式各样的物体,这些物体的颜色、材料可能不尽相同,但是形状、大小是一致的。也就是说,一类物体,必须存在共同的部分,才能够抽象出一个模具,模具的作用就是重复利用这些共同之处,减少重复性。 编程是工具,本质是解决问题,解决问题考验的就是抽象化和具体化的能力。抽象化应对的是现实世界中的不变,而具体化应对的是现实世界中的变化,能够清楚地认识到变与不变是什么,问题也就迎刃而解了。那先来分清抽象化和具体化的概念。 抽象的意思是,在许多事物中,去除非本质的属性,抽出本质属性。抽象化就是呈现出具体事物共同本质的过程。将复杂的现实,简化成单纯的模型,这种抽象化方式称为模型化,所谓问题建模,就是对问题进行模型化,也即抽象的过程。具体的概念相对简单,就是指看得见摸得着的事物,每个事物都是独一无二的,是以变化尤盛。具体化就是将抽象事物加载清晰的过程,是一种有助于理解陌生事物的方式。数据类型是具体事物,能够清晰简明地呈现出不同数据类型的共同逻辑,就是用模板类型代替具体类型的精妙之处。 模板本身是一种抽象化的工具,以其编写的是抽象逻辑,表示某类类型的共有本质,是不变的部分。其本身是没有用的,实际需要的还是具体逻辑,因此需要有具体化的过程,将抽象逻辑转变为具体逻辑。在 C++ 模板中,这种将抽象逻辑转变成具体逻辑的过程,称为实例化。实例化将在编译期将抽象类型替换成具体类型,根据模板生成一个个具体逻辑。这种实例化机制,便是我们所需要的代码生成能力。 与宏不同,模板具体化后生成的代码,并无法直接在生成的源码中看到,但是可以通过 CppInsight 这类工具察看。 比如: template <typename T> inline constexpr T Integer = 42; int… Continue Reading 《产生式元编程》第五章 忆昔年模板三两事

宏部分完结 本系列断更良久,去年已更新前三章: 《产生式元编程》 第一章 宏编程计数引原理 《产生式元编程》 第二章 自复用代码生成技 《产生式元编程》 第三章 替换蓝染概念纤悉 宏部分的核心理论和技术,于此三章,悉已更讫。利用这些原理和技巧,可以实现一些简单的代码生成工具,得到初步的产生式元编程能力。 本系列原定十章,宏只是其中的第一个模块,属于上篇。上篇完结后,将产生一个产生式元编程库,再步入中篇。由是,本系列由虚向实,自理论技术诞生项目,复从项目推进续篇,循此迭代。 章章衔进,写来岂是容易?再有诸事相羁,停更半载有余,亦是无奈。近来乘暇推进,初版已卒,遂以续更。 本章乃承上启下之篇,为上篇画句号,为中篇起草稿。 GMP 产生式元编程库 本系列将同步产生一个产生式元编程库,名为 GMP,可在 GitHub 找到 https://github.com/lkimuk/gmp 。该库的侧重点是提供产生式编程工具,可在编译期实现代码生成。 基于前三章的内容,已添加宏模块的基本功能,跨平台,可实现简单的代码生成。其余模块将随着后续章节持续添加。 下面是一些使用例子。 获取可变宏参数数量: #include <gmp/gmp.hpp> int main() { // Output: 0 1 2 3 4 5 6 7 printf("%d %d %d %d %d %d %d %d\n", GMP_SIZE_OF_VAARGS(),… Continue Reading 《产生式元编程》第四章 封装合并框架顿立

前两章主要集中于应用实践,理论概念都是蜻蜓点水,本章将重点放在这些概念原理上,深入讲解一下。 宏二段替换 源文件扫描后,宏被替换为目标内容,替换实际上分为两个阶段。 第一阶段的替换发生在参数替换之时。 当宏函数含有参数时,该参数会被替换。只有两种情况例外,一是 token 前面含有 # 或 ## 预处理标记,二是后面紧跟着 ## 预处理标记。 例如: #define Def(x) x #define Function(x, y) x ## y #define Func(x) #x #define Fun(x) ##x // Not allowed #define Fn(x) x# // Not allowed #define Defn(x, y) # x ## y // Not allowed #define A()… Continue Reading 《产生式元编程》第三章 替换蓝染概念纤悉

衔引 原理毕,复用续。历观编程概念,避及重复,提其不变,于迭代与递归为甚。产生式元编程,乃欲自动产生百千代码,迭代递归,自是重要组件,不可不备。 本章以 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 《产生式元编程》第一章 宏编程计数引原理