|
template metaprogramming (TMP)(模板元编程)是写 template-based(基于模板)的运行于编译期间的 C 程序的过程。考虑一下:一个 template metaprogram(模板元程序)是用 C 写的运行于 C 编译器中的程序。当一个 TMP 程序运行完成,它的输出——从 templates(模板)实例化出的 C 源代码片断——随后被正常编译。
如果你仅把它看作古怪的特性而没有打动你,那你就不会对它有足够的深入的思考。
C 并不是为 template metaprogramming(模板元编程)设计的,但是自从 TMP 在 1990 年代早期被发现以来,它已被证明非常有用,使 TMP 变容易的扩展很可能会被加入到语言和它的标准库之中。是的,TMP 是被发现,而不是被发明。TMP 所基于的特性在 templates(模板)被加入 C 的时候就已经被引进了。所需要的全部就是有人注意到它们能够以一种精巧的而且意想不到的方式被使用。
TMP 有两个强大的力量。首先,它使得用其它方法很难或不可能的一些事情变得容易。第二,因为 template metaprograms(模板元程序)在 C 编译期间执行,它们能将工作从运行时转移到编译时。一个结果就是通常在运行时才能被察觉的错误能够在编译期间被发现。另一个结果是 C 程序使得 TMP 的使用在以下每一个方面都能更有效率:更小的可执行代码,更短的运行时间,更少的内存需求。(然而,将工作从运行时转移到编译时的一个结果就是编译过程变得更长。使用 TMP 的程序可能比它们的 non-TMP 对等物占用长得多的编译时间。)
考虑STL的advance伪代码。(在《C 箴言:为类型信息使用特征类》中。你现在可能需要读该文,因为在本文中,我假设你已经熟悉了该文的内容。),我突出表示代码中的伪代码部分:
template<typename IterT, typename DistT> void advance(IterT& iter, DistT d) { if (iter is a random access iterator) { iter = d; // use iterator arithmetic } // for random access iters else { if (d >= 0) { while (d--) iter; } // use iterative calls to else { while (d ) --iter; } // or -- for other } // iterator categories } 我们可以用 typeid 把伪代码变成真正的代码。这就产生了一个解决此问题的“常规”的 C 方法——它的全部工作都在运行时做:
template<typename IterT, typename DistT> void advance(IterT& iter, DistT d) { if (typeid(typename std::iterator_traits<IterT>::iterator_category) == typeid(std::random_access_iterator_tag)) {
iter = d; // use iterator arithmetic } // for random access iters else { if (d >= 0) { while (d--) iter; } // use iterative calls to else { while (d ) --iter; } // or -- for other } // iterator categories } 《C 箴言:为类型信息使用特征类》中指出这个 typeid-based(基于 typeid)的方法比使用 traits 的方法效率低,因为这个方法,(1)类型检测发生在运行时而不是编译期,(2)用来做运行时类型检测的代码必须出现在可执行代码中。实际上,这个例子展示了 TMP 如何能比一个“常规”C 程序更高效,因为 traits 方法是 TMP。记住,traits 允许编译时在类型上的 if...else 计算。
我先前谈及一些事情在 TMP 中比在“常规”C 中更简单,而 advance 提供了这方面的一个例子。Item 47 提到 advance 的 typeid-based(基于 typeid)的实现可能会导致编译问题,而这就是一个产生问题的例子:
std::list<int>::iterator iter;
...
advance(iter, 10); // move iter 10 elements forward; // won't compile with above impl. 考虑 advance 为上面这个调用生成的版本。用 iter 和 10 的类型取代 template parameters(模板参数)IterT 和 DistT 之后,我们得到这个:
void advance(std::list<int>::iterator& iter, int d) { if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) == typeid(std::random_access_iterator_tag)) {
iter = d; // error! } else { if (d >= 0) { while (d--) iter; } else { while (d ) --iter; } } } 问题在突出显示的行,使用了 = 的那行。在当前情况下,我们试图在一个 list<int>::iterator 上使用 =,但是 list<int>::iterator 是一个 bidirectional iterator(双向迭代器)(参见《C 箴言:为类型信息使用特征类》),所以它不支持 =。只有 random access iterators(随机访问迭代器)才支持 =。此时,我们知道我们永远也不会试图执行那个 = 行,因为那个 typeid 检测对于 list<int>::iterators 永远不成立,但是编译器被责成确保所有源代码是正确的,即使它不被执行,而当 iter 不是一个 random access iterator(随机访问迭代器)时 "iter = d" 是不正确的。traits-based(基于 traits)的 TMP 解决方案与此对比,那里针对不同类型的代码被分离到单独的函数中,其中每一个都只使用了可用于它所针对的类型的操作。
TMP 已经被证明是 Turing-complete(图灵完备)的,这意味着它强大得足以计算任何东西。使用 TMP,你可以声明变量,执行循环,编写和调用函数,等等。但是这些结构看起来与其在“常规”C 中的样子非常不同。例如,《C 箴言:为类型信息使用特征类》展示了 if...else 条件在 TMP 中是如何通过 templates(模板)和 template specializations(模板特化)被表达的。但那是 assembly-level(汇编层次)的 TMP。针对 TMP 的库提供了一种更高层次的语法,虽然还不至于让你把它误认为是“常规”C 。
为了一窥其它东西在 TMP 中如何工作,让我们来看看 loops(循环)。TMP 中没有真正的
[1] [2] [3] 下一页
|