C++模板元编程中模板特化的概念从何而来?),是C++语言的扩展。更极端的理解:它是一种新的图灵完备编程语言(即C++模板可以实现图灵机模型中的所有功能)。在《Modern C++ Design》中,作者提出了以下问题:(1)如何编写更高级的C++程序?(2)即使在干净的设计中,如何处理大量不相关的细节?(3)如何构建可重用的组件,这样你就不用每次在不同的程序中应用组件时都要开战了?解决上述问题的方法是模板元编程。Meta(元)本身就是一个很“抽象(abstract)”的词,因为它的本义是“抽象”。元编程也可以说是“编程抽象”。更好的理解,元编程就是你写了一个程序A,程序A运行后会生成另一个程序B,而程序B就是真正实现功能的程序。那么这时候程序A就可以称为程序B的元程序,编写程序A的过程就叫做“元编程”。在C++中,元编程的手段可以是宏,也可以是模板。1.为什么需要泛型编程:从宏到模板,再到元编程?如果元编程中的所有变量(或者说元编程的参数)都是类型,那么我们对这种编程有一个专门的称呼,叫做“泛型”。发明模板只是为了完成与宏几乎相同的替换工作吗?可以说是,也可以说不是。一方面,模板可以用来代替类型,这与宏没有区别。只是宏在编译阶段纯粹是根据文本进行替换,被替换的文本本身是没有语义的。在分析模板和实例化模板的时候都会对模板进行检查,源码也可以和调试符号一一对应,所以不管是编译还是运行,排错都比较简单。另一方面,模板和宏之间也有很大的区别。模板之间最大的区别在于它们是“可操作的”。我们来看一个例子:voidAdd(uint8_t,unit8_t){}上面的函数实现了uint8_t和uint8_t类型的加法运算。如果要实现int16和int16类型的加法运算怎么办呢?更简单的方法如下:if(type==8){Add(uint8_t,uint8_t)}elseif(type==16){Add(uint16_t,uint16_t)}但是这里有两个难点:首先,if(type==x)isnotpresent其次,在C++中,即使有获取变量类型的方法(Boost.Any中的typeid),我们也不希望在运行时对其进行判断,这样会变得很慢。是否可以在不引入if/else的情况下,在编译时判断Add方法?有人说重载和虚函数也可以解决上面的问题:voidAdd(uint8_t,uint8_t){}voidAdd(uint16_t,uint16_t){}甚至在C语言中定义一个新的结构体Variant或者使用void*也可以解决这个问题:structVariant{union{uint_8x;uint_16y;}data;uint32_ttypeId;};可以,但是如果我还有uint9_t、uint10_t等各种类型,那加法操作呢?反正不管用哪种方法,都很难避免if/else的存在。模板与上述方法最大的区别在于,模板无论其参数或类型如何,都是一种编译时派发方法。可以在编译时确定的东西可以进行类型检查,编译器也可以进行优化,切断任何不必要的代码执行路径。2.类模板特化:模板世界中的if/else2.1根据类型执行代码先来看一个模板的例子:templateTAddFloatOrMulInt(Ta,Tb);//我们要这个函数在T是float的时候做加法,T是int类型的时候做乘法。那么当传入两个不同类型的变量,或者不是int和float的变量时,编译器就会提示错误。在能力方面,模板能做的一切都是在编译时完成的。编译期的完成意味着当你编译一个程序时,所有的数量都已经确定了。比如下面这个例子:inta=3,b=5;VariantaVar,bVar;aVar.setInt(a);//我们新加的方法不管怎么实现的,只要大家看懂意思即可。bVar.setInt(b);Variantresult=AddFloatOrMulInt(aVar,bVar);从上面的代码我们可以看出aVar和bVar都必须是整数。所以如果有合适的机制,编译器可以知道这里的AddFloatOrMulInt中只需要执行int路径上的代码,编译器也可以在这里单独为int路径生成代码,从而去掉不必要的if。在模板代码中,这种“适当的机制”指的是“特化”和“部分特化(PartialSpecialization)”,后者也称为“部分特化”。2.2.如何编写模板专业化代码版本1.0-伪代码int/floatAddFloatOrMulInt(a,b)//类的静态函数{if(typeisint){returna*b;}elseif(typeisfloat){returna+b;}}voidfoo(){floata,b,c;c=addFloatOrMulInt(a,b);//c=a+b;intx,y,z;z=addFloatOrMulInt(x,y);//z=x*y;}2.0版本——函数重载//c=a+b;intx,y,z;z=AddFloatOrMulInt(x,y);//z=x*y;}3.0version-puretemplate//这是给float用的。templateclassAddFloatOrMulInt{TDo(Ta,Tb){returna+b;}};//这是给int用的。templateclassAddFloatOrMulInt{TDo(Ta,Tb){returna*b;}};voidfoo(){floata,b,c;//我们需要c=a+b;c=AddFloatOrMulInt::Do(a,b);//...感觉不对...//啊!有两个AddFloatOrMulInt,类看起来一模一样,怎么区分呢!}好了,问题来了!如何区分两个内容不同但模板参数形式相同的类?专业化!特化是基于一个或多个特殊的整数或类型,在实例化时赋予模板一个指定的内容。Version4.0-Templatespecialization//先写模板的通用形式(原型,即初始化,不能保存)templateclassAddFloatOrMulInt{staticTDo(Ta,Tb)//注意这里必须是静态方法!!!{returnT(0);}};//其次,我们需要指定T为float时的代码:template<>classAddFloatOrMulInt{public:staticfloatDo(floata,floatb){returna+b;}};//同样,我们要指定T为int时的代码,这是专业化:float>::Do(1.0,2.0);}intmain(){std::cout<classAddFloatOrMulInt;//但是这个类是在T为int的时候用到的,所以我们写成classAddFloatOrMulInt;//当然这里的编译是不能通过的。//但它不是普通的类,而是类模板的特化(特例)。//所以需要添加模板关键字template和模板参数列表template*这里填写什么?*/>classAddFloatOrMulInt;//最后,模板参数列表要填什么?因为原型T已经被int代替了。所以你不能也不需要在这里放置任何额外的参数。所以这里留空。template<>classAddFloatOrMulInt{//...int的实现...}//完成!至此,第一个模板特化的代码已经编写完成。这里的AddFloatOrMulInt就像一个函数,但它只能在编译期间执行。如果你经历过这些,那么恭喜你,你的模板元编程已经开窍了。3.小结本文核心只讲两个问题:一是为什么要泛型编程,重点讨论宏、模板和元编程的关系;二是如何编写模板类的专用代码。专业化的细节知识还是很多的。我们将在后续文章中继续探讨。此外,我们还会介绍偏特化等知识点,敬请期待。