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?

3.1 再次分析Item 47中的实例

考虑在Item 47中为STL的advance写出来的伪代码。我已经为伪代码部分做了粗体:

 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 >= ) { 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 >= ) { while (d--) ++iter; } // use iterative calls to
else { while (d++) --iter; } // ++ or -- for other
} // iterator categories
}

Item 47指出这种基于typeid的方法比使用trait效率更低,因为通过使用这种方法,(1)类型测试发生在运行时而不是编译期(2)执行运行时类型测试的代码在运行的时候必须可见。事实上,这个例子也展示出了为什么TMP比一个“普通的”C++程序更加高效,因为traits方式属于TMP。记住,trait使得在类型上进行编译期if…else运算成为可能。

我已经在前面提到过一些东西说明其在TMP中比在“普通”C++中更加容易,Item 47中也提供了一个advance的例子。Item 47中提到了advance的基于typeid的实现会导致编译问题,看下面的例子:

 std::list<int>::iterator iter;
...
advance(iter, ); // move iter 10 elements forward;
// won’t compile with above impl.

考虑为上面调用所产生的advance的版本,将模板参数IterT和DistT替换为iter和10的类型之后,我们得到下面的代码:

 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! won’t compile }
else {
if (d >= ) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}

有问题的是高亮部分,就是使用+=的语句。在这个例子中,我们在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中没有真正的循环的概念,所以循环的效果是通过递归来完成的。(如果一提到递归你就不舒服,在进入TMP 冒险之前你就需要处理好它。TMP主要是一个函数式语言,递归对于函数式语言就如同电视对美国流行文化一样重要:它们是不可分割的。)即使是递归也不是普通的递归,因为TMP循环没有涉及到递归函数调用,所涉及到的是递归模板实例化(template instantiations)。

TMP的“hello world”程序是在编译期计算阶乘。它算不上是令人激动的程序,“hello world”也不是,但是这两个例子对于介绍语言都是有帮助的。TMP阶乘计算通过对模板实例进行递归来对循环进行示范。也同样示范了变量是如何在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->::value };
};
template<> // special case: the value of
struct Factorial<> { // Factorial<0> is 1
enum { value = };
};

考虑上面的模板元编程(真的仅仅是单一的元函数Factorial),你通过引用Factorial<n>::value来得到factorial(n)的值。

代码的循环部分发生在模板实例Factorial<n>引用模板实例Factorial<n-1>的时候。像所有递归一样,有一种特殊情况来让递归终止。在这里是模板特化Factorial<0>。

每个Factorial模板的实例都是一个结构体,每个结构体使用enum hack(Item 2)来声明一个叫做value的TMP变量。Value持有递归计算的当前值。如果TMP有一个真正的循环结构,value将会每次循环的时候进行更新。既然TMP使用递归模板实例来替换循环,每个实例会得到它自己的value的拷贝,每个拷贝都会有一个和“循环”中位置想对应的合适的值。

你可以像下面这样使用Facorial:

 int main()
{
std::cout << Factorial<>::value; // prints 120 std::cout << Factorial<>::value; // prints 3628800 }

如果你认为这比冰激凌更酷,你就已经获得模板元程序员需要的素材。如果模板和特化,递归实例和enum hacks,还有像Factorial<n-1>::value这样的输入使你毛骨悚然,你还是一个“普通的”C++程序员。

3.4 TMP还能够做什么?

当然,Factorial对TMP的功能进行了示范,如同“hello world”程序对任何传统编程语言的功能进行示范一样。为了让你明白为什么TMP是值得了解的,知道它能够做什么很重要,这里有三个例子:

  • 确保因次单位(dimensional unit)的正确性。在科学和工程应用中,把因次单位(例如,质量,距离和时间)正确的拼到一起是很必要的。将表示质量的变量赋值给表示速度的变量是错误的,但是用距离变量除以时间变量然后将结果赋值被速度变量就没有问题。通过使用TMP,确保(在编译期间)程序中的所有因次单元组合的正确性就是可能的,不管计算有多复杂。(这也是使用TMP来侦测早期错误的一个例子。)TMP这种用法的一个有趣的方面是它能够支持分数因次的指数。这需要在编译期间将分数简化,然后编译器才能够确认,例如,单元 time1/2同time4/8是相同的。
  • 优化矩阵操作。Item 21中解释了有一些函数(包括 operator*)必须返回新的对象,Item 44中引入了SquareMatrix类,考虑下面的代码:
    1 typedef SquareMatrix<double, 10000> BigMatrix;
    2 BigMatrix m1, m2, m3, m4, m5; // create matrices and
    3 ... // give them values
    4 BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product

    用“普通的”方式来计算result会有四次创建临时matrice对象的调用,每次调用都应用在对operator*调用的返回值上面。这些独立的乘法在矩阵元素上产生了四          次循环。使用TMP的高级模板技术——表达式模板(expression templates),来消除临时对象以及合并循环是有可能的,并且不用修改上面的客户端代码的语法。最   后的程序使用了更少的内存,而且运行速度会有很大的提升。

  • 产生个性化的设计模式实现。像策略模式,观察者模式,访问者模式等等这些设计模式能够以很多方式被实现。使用基于模板的技术被叫做policy-based设计,我们可以创建表示独立设计选择(choice或者叫”policies”)的 模板,这些模板可以以任意的方式进行组合来产生个性化的模式实现。例如,使用这种技术能够创建一些实现智能指针行为策略(policies)的模板,使用它能够产生(在编译期)上百种不同的智能指针类型。这项技术已经超越了编程工艺领域,如设计模式和智能指针,它成为了生殖编程(generative programming)的基础。

4. TMP现状分析

TMP并不是为每个人准备的。因为语法不直观,支持的相关工具也很弱。(像为模板元编程提供的调试器。)作为一个“突然性“的语言它只是最近才被发现的,TMP编程的一些约定正在实验阶段。然而通过将工作从运行时移到编译期所带来的效率提升带给人很深刻的印象,对一些行为表达的能力(很难或者不可能在运行时实现)也是很吸引人的。

对于TMP的支持正在上升期。很可能下个版本的C++就是显示的支持它。TR1中已经支持了(Item 54)。关于这个主题的书籍已经开始出来了,网上的一些关于TMP信息也越来越多。TMP可能永远不会成为主流,但是对于一些程序员来说——尤其是程序库的实现者——几乎必然会成为主要手段。

5. 总结

  • 模板元编程可以将工作从运行时移到编译期,这样可以更早的发现错误,并且提高运行时性能。
  • 基于策略选择(policy choices)的组合TMP能够被用来产生个性化的代码,也能够用来防止为特定类型生成不合适的代码。

effective c++ Item 48 了解模板元编程的更多相关文章

  1. 读书笔记 effective c++ Item 48 了解模板元编程

    1. TMP是什么? 模板元编程(template metaprogramming TMP)是实现基于模板的C++程序的过程,它能够在编译期执行.你可以想一想:一个模板元程序是用C++实现的并且可以在 ...

  2. 初识c++模板元编程

    模板元编程(Template metaprogramming,简称TMP)是编译器内执行的程序,编译器读入template,编译输出的结果再与其他源码一起经过普通编译过程生成目标文件.通俗来说,普通运 ...

  3. C++模板元编程(C++ template metaprogramming)

    实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版) 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得 ...

  4. C++模板元编程 - 函数重载决议选择工具(不知道起什么好名)完成

    这个还是基于之前实现的那个MultiState,为了实现三种类型“大类”的函数重载决议:所有整数.所有浮点数.字符串,分别将这三种“大类”的数据分配到对应的Converter上. 为此实现了一些方便的 ...

  5. C++模板元编程 - 挖新坑的时候探索到了模板元编程的新玩法

    C++真是一门自由的语言,虽然糖没有C#那么多,但是你想要怎么写,想要实现什么,想要用某种编程范式或者语言特性,它都会提供. 开大数运算类的新坑的时候(又是坑),无意中需要解决一个需求:大数类需要分别 ...

  6. 读书笔记_Effective_C++_条款四十八:了解模板元编程

    作为模板部分的结束节,本条款谈到了模板元编程,元编程本质上就是将运行期的代价转移到编译期,它利用template编译生成C++源码,举下面阶乘例子: template <int N> st ...

  7. c++ 模板元编程的一点体会

    趁着国庆长假快速翻了一遍传说中的.大名鼎鼎的 modern c++ design,钛合金狗眼顿时不保,已深深被其中各种模板奇技淫巧伤了身...论语言方面的深度,我看过的 c++ 书里大概只有 insi ...

  8. C++模板元编程 - 3 逻辑结构,递归,一点列表的零碎,一点SFINAE

    本来想把scanr,foldr什么的都写了的,一想太麻烦了,就算了,模板元编程差不多也该结束了,离开学还有10天,之前几天部门还要纳新什么的,写不了几天代码了,所以赶紧把这个结束掉,明天继续抄轮子叔的 ...

  9. C++模板元编程 - 2 模仿haskell的列表以及相关操作

    这是昨天和今天写的东西,利用C++的可变模板参数包以及包展开,模式匹配的一些东西做的,感觉用typename...比轮子叔那个List<A,List<B, List<C, D> ...

随机推荐

  1. Axiom3D学习日记 0.Axiom基础知识

    Axiom 3D Engine An open-source, cross-platform, managed 3D rendering engine for DirectX, XNA and Ope ...

  2. (一)SAPI简述

    SAPI,软件中的语音技术包括两方面的内容,一个是语音识别(speech recognition) 和语音合成(speech synthesis).这两个技术都需要语音引擎的支持. 下面我们来了解下基 ...

  3. OpenCV——IplImage

    IplImage结构: typedef struct _IplImage { int nSize; /* IplImage大小 */ int ID; /* 版本 (=0)*/ int nChannel ...

  4. python汉字输出编码问题

    python中文输出乱码问题困扰了多少初学者,我在这方面栽了不知道多少跟头.现在我把我碰到的问题和解决的方法写出来与大家分享一下: 1输出乱码 所谓的乱码是指“鎴戞槸涓枃瀛楃涓”这样的内容.为什么 ...

  5. asp.net 下的中文分词检索工具 - jieba.net

    jieba是python下的一个检索库, 有人将这个库移植到了asp.net 平台下, 完全可以替代lucene.net以及盘古分词的搭配 之所以写这个, 其实是因为昨天面试时, 被问到网站的关键字检 ...

  6. spring的配置和使用

    spring的配置和使用 1. 创建基于java的配置. 配置极少量的XML来启用java配置:   <?xml version="1.0" encoding="U ...

  7. layer.conifrm 非阻塞执行 ztree删除节点 问题

    layer.confirm无法阻塞js执行,导致ztree插件的beforeRemove回调函数未等待用户确定删除便已经移除界面中的节点, 因此可能会出现前后台数据不一致情况,正常逻辑理应删除后台数据 ...

  8. jvm运行机制和volatile关键字详解

    参考https://www.cnblogs.com/dolphin0520/p/3920373.html JVM启动流程 1.java虚拟机启动的命令是通过java +xxx(类名,这个类中要有mai ...

  9. c++ String 大小写转化

    string toUpperString(string str) { transform(str.begin(), str.end(), str.begin(), (int (*)(int))toup ...

  10. nginx转发请求

    location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_add ...