熟悉C++的程序员都知道,C++是一门多范式编程语言,支持面向过程、面向对象、泛型编程以及函数式编程范式。然而提到C++模板元编程,在很多人心里这却是C++里的黑魔法:它很难学习,一旦进入这个领域曾经那些熟悉的东西(if,for...)都不再灵验;它很强大,但现实中却鲜见有人用它来解决实际问题,除过偶尔在一些编码练习中被某些C++狂热粉当做奇淫巧技拿出来秀秀肌肉。
其实模板元编程是C++所支持的一种非常强大的计算能力,它是使用C++开发高质量库和框架所离不开的一项武器。
掌握C++模板元编程,至少可以在以下场合帮助到你:
- 实现高扩展性,并且兼具高性能的库
- 实现灵活且易于使用的框架
- 实现基于C++的内部DSL(Domain Specific Language)
- 帮助更深入地理解并使用模板和泛型编程,更好地去使用C++ STL库中的高级特性
如果你是一个C++的库或框架的开发者,了解和掌握一些模板元编程的知识,可以让你的作品更易于扩展、拥有更易用的接口,甚至更高的运行时效率。而即使你只使用C++设计和开发应用程序,了解模板元编程也会帮助你更好的去使用STL库的各种特性,帮助你的局部设计做的更加漂亮。
实际上C++模板元编程技术已经渗透在我们日常使用的各种库和框架中,例如我们最常使用的STL库以及各种xUnit测试框架和mock框架。可以说,模板元编程是中级C++程序员迈向高级的必经之路!
然而现实是,C++模板元编程的学习之旅却并不平坦。
一方面,由于C++模板元编程的本质是函数式编程,熟练掌握并使用函数式编程的程序员比较小众,绝大多数程序员初次进入这个领域时面对模式匹配、递归和不可变性时都会手足无措。另一方面,由于C++的模板元编程能力是被意外发现的,不像别的函数式编程语言经过良好的设计,所以C++的这种函数式计算能力天生存在着各种缺陷,直到C++11标准才开始逐渐完善起来。在之前很多重要特性都靠很绕的方式去迂回实现,增加了学习的难度。
另外,市面上介绍模板元编程的书和资料也乏善可陈,以下的书相对还不错,但对于模板元编程的介绍仍旧存在一些问题:
《C++ Templates Complete》
介绍C++模板知识最全面的一本书,涉及到了模板各个方面的基础知识和应用技巧。由于元编程并不是此书重点,所以仅有短短一章列举了一些利用模板元编程做数值计算的例子。现实中使用模板元编程单纯做数值计算的场合并不多,模板元编程更大的价值在于做类型计算和代码生成,书中却涉及甚少。《Modern C++ Design》
介绍如何使用模板进行C++高阶设计的一本书。介绍了TypeLists
的概念,作为一种重要的编译时数据结构,是元编程的基础。但遗憾作者并没有明确的提出元编程的概念,也没有对C++编译时的运算特征进行总结和提炼。最后由于此书基于的C++ 98标准对于模板以及编译期类型计算支持上的欠缺,书中介绍的不少实现比较迂回复杂。《C++模板元编程》
正式介绍C++模板元编程的一本书,引入了元函数的概念。通过对模板计算的规范化,发挥了编译期元函数可组合的优势。遗憾的是,此书只能算是boost中mpl库的用户手册,基本上是在讲mpl库的用户接口和使用方法,没有涉及库的实现细节。现实中我们采用元编程解决问题,一般不会引用boost这么重的库,往往只会在某一局部借鉴类似的设计技巧。所以对于元编程来说,授之以渔的意义远大于授之以鱼。另外由于boost mpl库中用了大量C++预处理期代码生成技术,导致通过阅读mpl源码去掌握模板元编程的同学一上来就陷入到一堆宏中,对于学习元编程增加了非常多的干扰因素。
基于以上原因,我基于C++11标准实现了一个模板元编程的基础库:TLP (https://github.com/MagicBowen/tlp),然后再通过本手册来为大家全面介绍C++模板元编程的基本知识和使用技巧。
TLP库包含以下内容:
- 基本的编译期数据类型和算法;
- 基本的编译期数据结构
TypeList
,以及针对它的各种基本算法函数:length、append、erase、replace、unique、belong、comb... - 基于
TypeList
的各种高阶函数,如 any,all,sort、transform、map,filter,fold... - 辅助模板元编程的常用函数,如 __is_eq(),__if(),__print() ...
- 一些有用的编译期Traits工具,如 IsBaseOf,IsConvertible,IsBuiltIn...
- 一些常用的元编程模式,如 “元函数转发”等;
- 一个面向模板元编程的测试框架,用它描述的所有测试用例执行在C++的编译期,我们使用它来对TLP进行测试;
除此之外,TLP库中还包含了如下示例代码,用来演示如何在现实场景中应用好模板元编程和TLP库:
- 示范了如何使用模板元编程做纯编译期计算,完成一个自动数三角形的程序;
- 示范了如何使用模板元编程技术来实现代码生成,自动创建visitor设计模式;
- 示范了如何使用模板元编程技术来实现一个DSL,用于描述并生成有限状态机;
上面提到的TypeList
以及使用代码生成来创建visitor设计模式
的原创者是《Modern C++ Design》的作者Andrei Alexandrescu。在TLP中我用C++11对TypeList
及其算法进行了改写,并进行了高阶函数的扩展。得益于C++11标准对模板元编程的更好支持,新的实现比起原来的更加清晰和简洁。
示例代码中利用模板元编程创建有限状态机DSL
的设计最初来自于《C++模板元编程》一书,为了让其更好被理解,我对例子以及代码进行了较大的改编。
除了上面的例子,本手册中还介绍了我自己开发的针对C++模块和子系统FT(Functional Test)级别的测试框架dates,展示了它如何使用模板元编程来进行类型萃取、类型选择以及类型校验,最终使得框架变得更易用、更高效以及更安全。这些技巧可以帮助到大家更好地使用模板元编程去解决现实问题。
最后,TLP库自身的测试通过一个原创的C++模板元编程测试框架。该框架专门针对C++编译期计算进行测试,它的用法和常见的xUnit测试框架类似,但有趣的是使用该框架描述的所有测试用例的执行发生在C++编译期。本文会专门介绍该框架的一些实现细节。
C++模板元编程当年被提出来的时候,函数式编程还没有像今天这样被更多的人所了解和接受,当时的C++标准和编译器对模板和编译期计算的支持也存在着很多缺陷。然而到了今天,很多事情发生了变化!本文的读者最好能够有一些函数式编程的基础,了解C++模板的基本用法,熟悉一些C++11标准的内容。当然文中所有用的到模板技术、C++11标准和涉及到的函数式编程概念也都会专门介绍和说明。
如果你从来没有接触过C++模板元编程,那么最好从一开始就把它当做一门全新的语言去学习,从头掌握C++中这种不一样的计算模型和语法。这种新的语言和我们熟识的运行期C++在语法和计算模型上都有较大的差异,但它的优势在于能和运行期C++紧密无缝地结合在一起,无论是在提高程序可扩展性还是提高程序运行效率上,都能创造出非常不可思议的效果来。希望通过本手册,可以让更多的人掌握C++模板元编程这一设计利器,在适合的场合下以更有效、更酷的方式去解决问题。
文中出现的所有代码片段,如果在注释中给出了TLP库中对应的文件路径,则都能在TLP库中找到源码。除此之外的其它代码则是为了文章需要所构造的临时代码。另外,为了减少干扰,本文中所示TLP库中的代码均没有加命名空间,阅读文章和TLP库源码时请注意区分。