1. TMP是什么?
模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行。你可以想一想:一个模板元程序是用C++实现的并且可以在C++编译器内部运行的一个程序,它的输出——从模板中实例化出来的C++源码片段——会像往常一样被编译。
2. 使用TMP的优势
如果这没有冲击到你,是因为你没有足够尽力去想。
C++不是为了模板元编程而设计的,但是自从TMP早在1990年被发现之后,它就被证明是非常有用的,为了使TMP的使用更加容易,在C++语言和标准库中加入了一些扩展。是的,TMP是被发现的,而不是被发明。当模板被添加到C++中的时候TMP这个特性就被引入了。对于某些人来说所有需要做的就是关注如何以一种聪明的和意想不到的方式来使用它。
TMP有两种强大的力量。第一,它使得一些事情变得容易,也即是说如果没有TMP,这些事情做起来很难或者不可能实现。第二,因为模板元编程在C++编译期执行,它们可以将一些工作从运行时移动到编译期。一个结果就是一些原来通常在运行时能够被发现的错误,现在在编译期就能够被发现了。另外一个结果就是使用TMP的C++程序在基本上每个方面都更加高效:更小的执行体,更短的运行时间,更少的内存需求。(然而,将工作从运行时移到编译期的一个后果就是编译时间增加了。使用TMP的程序比没有使用TMP的程序可能消耗更长的时间来进行编译。)
3. 如何使用TMP?
考虑在Item 47中为STL的advance写出来的伪代码。我已经为伪代码部分做了粗体:
1 template<typename IterT, typename DistT>
2 void advance(IterT& iter, DistT d)
3 {
4 if (iter is a random access iterator) {
5
6 iter += d; // use iterator arithmetic
7
8 } // for random access iters
9
10 else {
11
12
13 if (d >= 0) { while (d--) ++iter; } // use iterative calls to
14 else { while (d++) --iter; } // ++ or -- for other
15 } // iterator categories
16 }
我们可以使用typeid替换伪代码,让程序能够执行。这就产生了一个“普通的”C++方法——也就是所有工作都在运行时开展的方法:
1 template<typename IterT, typename DistT>
2 void advance(IterT& iter, DistT d)
3 {
4 if ( typeid(typename std::iterator_traits<IterT>::iterator_category) ==
5 typeid(std::random_access_iterator_tag)) {
6
7 iter += d; // use iterator arithmetic
8
9 } // for random access iters
10
11 else {
12
13
14 if (d >= 0) { while (d--) ++iter; } // use iterative calls to
15 else { while (d++) --iter; } // ++ or -- for other
16 } // iterator categories
17 }
Item 47指出这种基于typeid的方法比使用trait效率更低,因为通过使用这种方法,(1)类型测试发生在运行时而不是编译期(2)执行运行时类型测试的代码在运行的时候必须可见。事实上,这个例子也展示出了为什么TMP比一个“普通的”C++程序更加高效,因为traits方式属于TMP。记住,trait使得在类型上进行编译期if…else运算成为可能。
我已经在前面提到过一些东西说明其在TMP中比在“普通”C++中更加容易,Item 47中也提供了一个advance的例子。Item 47中提到了advance的基于typeid的实现会导致编译问题,看下面的例子:
1 std::list<int>::iterator iter;
2 ...
3 advance(iter, 10); // move iter 10 elements forward;
4 // won’t compile with above impl.
考虑为上面调用所产生的advance的版本,将模板参数IterT和DistT替换为iter和10的类型之后,我们得到下面的代码:
1 void advance(std::list<int>::iterator& iter, int d)
2 {
3 if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
4 typeid(std::random_access_iterator_tag)) {
5
6 iter += d; 7
8 // error! won’t compile
9
10
11 }
12 else {
13 if (d >= 0) { while (d--) ++iter; }
14 else { while (d++) --iter; }
15 }
16 }
有问题的是高亮部分,就是使用+=的语句。在这个例子中,我们在list<int>::iterator上使用+=,但是list<int>::iterator是一个双向迭代器(见Item 47),所以它不支持+=。只有随机访问迭代器支持+=。现在,我们知道了+=这一行将永远不会被执行到,因为为list<int>::iteraotr执行的typeid测试永远都不会为真,但是编译器有责任确保所有的源码都是有效的,即使不被执行到,当iter不是随机访问迭代器“iter+=d”就是无效代码。将它同基于tratis的TMP解决方案进行比较,后者把为不同类型实现的代码分别放到了不同的函数中,每个函数中进行的操作只针对特定的类型。
3.2 TMP是图灵完全的
TMP已经被证明是图灵完全的(Turing-Complete),这也就意味着它足够强大到可以计算任何东西。使用TMP,你可以声明变量,执行循环,实现和调用函数等等。但是这些概念同“普通”C++相对应的部分看起来非常不同。例如,Item 47中if…else条件在TMP中是如何通过使用模板和模板特化来表现的。但这是程序级别(assembly-level)的TMP。TMP库(例如,Boost MPL,见Item 55)提供了更高级别的语法,这些语法不会让你误认为是“普通的”C++。
3.3 TMP中的循环通过递归来实现
再瞥一眼事情在TMP中是如何工作的,让我们看一下循环。TMP中没有真正的循环的概念,所以循环的效果是通过递归来完成