[NextPage]
TMP 的 \"hello world\" 程序在编译期间计算一个阶乘。它不是一个很令人兴奋的程序,不过,即使不是 \"hello world\",也有助于语言入门。TMP 阶乘计算示范了通过 recursive template instantiation(递归模板实例化)实现循环。它也示范了在 TMP 中创建和使用变量的一种方法。看:
| template<unsigned n> // general case: the value of struct Factorial { // Factorial<n> is n times the value // of Factorial<n-1> enum { value = n * Factorial<n-1>::value }; }; template<> // special case: the value of struct Factorial<0> { // Factorial<0> is 1 enum { value = 1 }; }; |
给出这个 template metaprogram(模板元程序)(实际上只是单独的 template metafunction(模板元函数)Factorial),你可以通过引用 Factorial<n>::value 得到 factorial(n) 的值。
代码的循环部分出现在 template instantiation(模板实例化)Factorial<n> 引用 template instantiation(模板实例化)Factorial<n-1> 的地方。就像所有正确的 recursion(递归)有一个导致递归结束的特殊情况。这里,它就是 template specialization(模板特化)Factorial<0>。
Factorial template 的每一个 instantiation(实例化)都是一个 struct,而每一个 struct 都使用 enum hack声明了一个名为 value 的 TMP 变量。value 用于持有阶乘计算的当前值。如果 TMP 有一个真正的循环结构,value 会在每次循环时更新。因为 TMP 在循环的位置使用 recursive template instantiation(递归模板实例化),每一个 instantiation(实例化)得到它自己的 value 的拷贝,而每一个拷贝拥有适合于它在“循环”中所处的位置的值。
你可以像这样使用 Factorial:
| int main() { std::cout << Factorial<5>::value; // prints 120 std::cout << Factorial<10>::value; // prints 3628800 } |
如果你觉得这比吃了冰淇淋还凉快,你就具有了一个 template metaprogrammer(模板元程序员)应有的素质。如果 templates(模板)以及 specializations(特化)以及 recursive instantiations(递归实例化)以及 enum hacks 以及对类似 Factorial<n-1>::value 这样的类型的需要使你毛骨悚然,好吧,你是一个不错的常规 C++ 程序员。
当然,Factorial 示范的 TMP 的效用大约就像 \"hello world\" 示范的任何常规编程语言的效用一样。为了领会为什么 TMP 值得了解,更好地理解它能做什么是很重要的。这里是三个示例:
Ensuring dimensional unit correctness(确保计量单位正确性)。在科学和工程应用中,计量单位(例如,质量,距离,时间,等等)被正确组合是基础。例如,将一个代表质量的变量赋值给一个代表速度的变量是一个错误,但是用一个时间变量去除距离变量并将结果赋给一个速度变量就是正确的。使用 TMP,不论计算多么复杂,确保(在编译期间)一个程序中所有计量单位组合都是正确的是有可能的。(这是一个如何用 TMP 进行早期错误诊断的例子。)这个 TMP 的使用的一个有趣的方面是能够支持分数指数。这需要这个分数在编译期间被简化以便于编译期能够确认,例如,单位 time1/2 与单位 time4/8 是相同的。
Optimizing matrix operations(优化矩阵操作)。《C++箴言:必须返回对象时别返回引用》中阐释了一些函数,包括 operator*,必须返回新的 objects,而《C++箴言:从模板中分离出参数无关的代码》一文中则引入了 SquareMatrix class,所以考虑如下代码:
| typedef SquareMatrix<double, 10000> BigMatrix; BigMatrix m1, m2, m3, m4, m5; // create matrices and ... // give them values BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product |
用“常规”方法计算 result 需要四个临时矩阵的创建,用于每一次调用 operator* 的结果。此外,独立的乘法产生了一个四次循环遍历矩阵元素的序列。使用一种与 TMP 相关的被称为 expression templates(表达式模板)的高级模板技术,完全不改变上面的客户代码的语法,而消除临时对象以及合并循环是有可能的。最终的软件使用更少的内存而且运行速度戏剧性地更快。
Generating custom design pattern implementations(生成自定义的设计模式实现)。像 Strategy(参见《C++箴言:考虑可选的虚拟函数的替代方法》),Observer,Visitor 等设计模式能用很多方法实现。使用一种被称为 policy-based design(基于 policy 设计)的 TMP-based(基于 TMP)的技术,使得创建代表独立的设计选择的 templates (\"policies\") 成为可能,这种 templates 能以任意的方法组合以产生带有自定义行为的模式实现。例如,这种技术经常用于允许几个实现了 smart pointer behavioral(智能指针行为)的 policies 的 templates 生成(在编译期间)数百个不同的 smart pointer(智能指针)类型。将类似设计模式和智能指针这样的编程器件的范围大大地扩展,这项技术是通常所说的 generative programming(产生式编程)的基础。
TMP 并不适合于每一个人。它的语法是不符合直觉的,工具支持也很弱(template metaprograms 的调试器?哈!)作为一个相对晚近才发现的“附属”语言,TMP programming 的规则仍然带有试验性质。然而,通过将工作从运行时转移到编译时所提供的效率提升还是能给人留下深刻的印象,而表达在运行时很难或不可能实现的行为的能力也相当有吸引力。
TMP 的支持程度在不断提升。很可能在 C++ 的下一个版本中将对它提供直接的支持,而且 TR1 已经这样做了。关于这一主题的书籍也即将开始出版(目前,C++ Template Metaprogramming: Concepts, Tools, and Techniques from Boost and Beyond 已经出版——译者注),而 web 上的 TMP 信息也正在保持增长。TMP 也许永远不会成为主流,但是对于某些程序员——特别是库开发者——它几乎必然会成为主料。
Things to Remember
·template metaprogramming(模板元编程)能将工作从运行时转移到编译时,这样就能够更早察觉错误并提高运行时性能。
