宏部分完结

本系列断更良久,去年已更新前三章:

宏部分的核心理论和技术,于此三章,悉已更讫。利用这些原理和技巧,可以实现一些简单的代码生成工具,得到初步的产生式元编程能力。

本系列原定十章,宏只是其中的第一个模块,属于上篇。上篇完结后,将产生一个产生式元编程库,再步入中篇。由是,本系列由虚向实,自理论技术诞生项目,复从项目推进续篇,循此迭代。

章章衔进,写来岂是容易?再有诸事相羁,停更半载有余,亦是无奈。近来乘暇推进,初版已卒,遂以续更。

本章乃承上启下之篇,为上篇画句号,为中篇起草稿。

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(),
        GMP_SIZE_OF_VAARGS(1),
        GMP_SIZE_OF_VAARGS('a', 'b'),
        GMP_SIZE_OF_VAARGS('a', 'b', 'c'),
        GMP_SIZE_OF_VAARGS('a', 'b', 1, 2),
        GMP_SIZE_OF_VAARGS('a', 'b', 1, 2, 3),
        GMP_SIZE_OF_VAARGS('a', 'b', 1, 2, 3, 4),
        GMP_SIZE_OF_VAARGS('a', 'b', 1, 2, 3, 4, 5));
}

生成下标索引:

#include <gmp/gmp.hpp>

int main() {

    // Output: 0 1 2 3 4 5 6 7 8 9
    printf("%d %d %d %d %d %d %d %d %d %d\n",
        GMP_MAKE_INDEX_SEQUENCE(10));
}

生成范围索引:

#include <gmp/gmp.hpp>

int main() {

    // Output: 10 11 12 13 14 15 16 17 18 19
    printf("%d %d %d %d %d %d %d %d %d %d\n",
        GMP_RANGE(10, 20));
}

批量生成变量:

#include <gmp/gmp.hpp>

#define DECLARE_VARIABLES(v) int variable_ ## v;

int main() {

    GMP_FOR_EACH(DECLARE_VARIABLES, GMP_RANGE(1, 4))

    variable_1 = 10;
    variable_2 = 20;
    variable_3 = 30;

    // Output: 10 20 30
    printf("%d %d %d\n", variable_1, variable_2, variable_3);
}

批量生成不变操作:

#include <gmp/gmp.hpp>

void bar(int arg1, const char* arg2) {
    printf("bar: %d, %s\n", arg1, arg2);
}

#define GENERATE_SOMETHING(arg1, arg2) bar(arg1, arg2);

int main() {
    GMP_LOOP(GENERATE_SOMETHING, 10, 42, "arg2")
}

// Output:
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2
// bar: 42, arg2

更多例子,后续再补。

基于现有功能,已能轻松实现许多代码生成需求,更加复杂的产生式需求,宏不太合适,以后通过其他模块来提供支持。

宏模块封装

GMP 中的宏模块,主要包含前三章所讲解的工具,但是要更加完善、易用。

所有功能都以产生式需求为导向,而非是为封装完善的预处理功能(如 BOOST_PP 预处理库),因此要更加精简且具有针对性。若是超级复杂的产生式需求,采用宏的奇技淫巧反而会使问题更加复杂,语法也难以直观。是以复杂的产生式需求,不会包含在宏模块当中。

宏模块包含的工具大体可分为:可变宏参数大小、宏拼接、宏重载、宏循环、宏递归、宏位操作、宏自增自减、宏布尔转换、宏条件逻辑、宏数值比较。

没有需求、或是其他模块能够更好地支持的功能,暂不会放在宏模块当中。

递归宏限制与跨平台

宏并非是图灵完备的系统,具有诸多限制,这是首先需要考虑的问题。

宏嵌套的最大深度不能超过 256 层,否则会产生编译错误,因此,一切索引都应该小于 256 层。这并非大问题,宏只是用来完成简单的代码生成,256 层递归完全可以满足基本需求。这样也为宏代码明确设置了一个上限,减少代码的行数。

虽然说最多有 256 层,但是辅助宏也会占用一定的层数,于是 GMP 的实际最大索引值是 254。

此外,不再使用 C++20 的相关特性,如 __VA_OPT__。因为 MSVC 默认不更新 __cplusplus,该值一直是 199711,需要使用 /Zc:__cplusplus 开启才会变化。这样就没必要像第一章那样分成 C++20 和之前的实现方式,统一用之前的实现方式更加简洁。

需要注意,MSVC 默认使用的是非标准宏,还需要使用 /Zc:preprocessor 开启标准宏,否则宏的行为和 C++ 标准不一致。

宏错误管理

报错对于宏来说也是非常关键的一个功能,没有报错,就无法检测索引是否超出最大限制,执行逻辑可能会产生无法预料的行为。

GMP 实现 GMP_CHECK_INDEX(index)GMP_CHECK_INDEX_BOOL(index) 来检查错误。前者将直接产生错误信息,后者将返回一个布尔值。

例如,调用如下操作:

GMP_MAKE_INDEX_SEQUENCE(255);

将存在错误,因为 GMP 的最大索引值是 254。不过不会发生未定义行为,GMP 通过前两个错误宏工具,使编译器可以产生错误。比如,使用 Clang 编译,将产生以下错误:

error: use of undeclared identifier 'Error_Index_255_Exceeds_Maximum_Macro_Index_254'

其他编译器也有相似的提示。这不仅在发生错误时能够中止程序逻辑,还提供了顾名思义的错误信息。

更多扩展

本系列还余数章未更,随着后续更新,GMP 也将逐步完善,添加代码生成功能。

可以说,本系列文章既是教程,也是 GMP 的开发文档,讲解了所有关键技术。

若已熟读本系列文章,熟悉所讲的理论和技术,欢迎向 GMP 作出贡献,实践所学,完善功能。

Leave a Reply

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

You can use the Markdown in the comment form.