首页 > 代码库 > 模板实参演绎
模板实参演绎
一、什么是实参演绎
如果我们每次都必须显式的指明模板替换参数类型,例如concat<std::string, int>(s, 3),那么过程将会显得非常繁琐。
如果我们可以concat(s, 3)//之前必须声明s是std::string类型,那么将会向普通的函数调用一样简单,
事实上,C++是允许这样的写法,然后C++编译器会根据实参(s和3)的类型推算出合适的替换类型。
如何演绎?
1 template<typename T>2 T const& max(T const& a T const& b)3 {4 return a < b ? b : a;5 }6 7 int g = max(1, 1.0);
上面的演绎会失败,为什么?因为“函数模板对应的参数化类型T”只有一种,但是“调用实参类型”(1和1.0的类型为int型和double型),会导致演绎矛盾,所以失败。
另一种失败的情况是替换的类型导致一个无效的构造。如下:
1 template<typename T> 2 typename T::ElementT at(T const& a, int i) 3 { 4 return a[i]; 5 } 6 7 void f(int* p) 8 { 9 int g = at(p, 1);// 这里会演绎失败,因为T被演绎成T*,但是int*没有一个叫做ElementT的替换类型。10 }
二、灵活的替换规则
为了方便我们下面的说明,我们来约定
来自调用实参类型 的类型为匹配类型A
来自函数模板对应的参数化类型T 的类型为匹配类型P
(1)如果被声明的参数 是一个带引用的声明(T& 或 T const&),那么匹配类型P(就是T 或 T const), A仍然是实参类型,不会忽略高层次的const和volatile限定符号。
(2)如果被声明的参数 是一个非引用的声明,P就是所声明的参数类型(比如T*),A仍然是实参类型, 同时还会忽略高层次的const和volatile限定符号。
(3)如果被声明的参数 是一个非引用的声明,如果这个实参是数组或者是函数类型(函数指针),那么还会发生decay转换,转换为对应的指针类型,同时还会忽略高层次的const和volatile限定符号。
(4)实参7不能传递给int&
template<typename T> void f(T); //P是 Ttemplate<typename T>void g(T&); //P是Tdouble x[20];int const seven = 7;f(x); //T被演绎成double*,decay转化g(x);//T被演绎成double[20]f(seven);//T被演绎成int,忽略了高层的constg(seven);//T被演绎成int const,不会忽略f(7);//T被演绎成intg(7);//T被演绎成int,但是7不能传递给int&
(5)又一个坑:实参为字符串,那么演绎的结果T应该是字符串数组,不是字符串指针char*
template<typename T>T const& max(T const& a, T const& b);
max("Apple", "Pear");“Apple”的类型是char const[6], “Pear”的类型是char const[5];而且不存在数组到指针的decay转型(因为演绎的参数是引用参数)。因此为了演绎成功,T就同时必须得是char[6]和char[5]。这显然是不可能的,所以产生错误。
三、练手
template<typename T>void f1(T*);template<typename E, int N>void f2(E(&)[N]);template<typename T1, typename T2, typename T3>void f3(T1(T2::*)(T3*));class S{public: void f(double*);};void g(int ***ppp){ bool b[42]; f1(ppp); //演绎T为int*** f2(b); //演绎E为bool, N为42 f3(&S::f); //演绎T1为void, T2为S,T3为double}
演绎上下文的过程:匹配从最顶层开始,然后不断的递归各种组成元素。
然而有两类构造不能用于演绎上下文。
(1)受限的类型名称,Q<T>::X的类型名称不能用来演绎模板参数T
(2)除了非类型参数外,模板参数还包含其他的成分的非类型表达式,诸如S<I+1>,int(&)[sizeof(S<T>)]类型不能用来演绎I和T
为什么?因为演绎过程并不唯一(甚至不一定有限的情况内演绎完毕)。
template<int N>class X{public: typedef int I; void f(int){}};
现在如果用 一个int 演绎typename X<N>::I ,那么是不成功的,为什么因为我可以有很多X<1>::i,X<2>::I, X<3>::I …… X<1000>::I,表示,所以C++禁止这种演绎。
template<int N>void fppm(void (X<N>::*p)(typename X<N>::I));int main(){ fppm(&X<33>::f);//可以演绎成功,N=33}
在函数模板fppm()中,子构造X<N>::I不是一个可以演绎的上下文,但是可以通过成员指针类型(X<N>::*p)的成员部分X<N>演绎上下文。
四、特殊情况的演绎
存在两种情况,其中演绎实参-参数对(A,P)并不是分别来自函数调用的实参,函数模板参数。
template<typename T>void f(T, T);void (*pf)(char, char) = &f;
(1)如上,第一种情况是取函数模板的地址的时候, A就是void(char, char), P就是void(T, T),
T被演绎成char,同时pf被初始化为“特化f<char>“的地址
------------------------我是华丽的分割线╮( ̄▽ ̄")╭------------------------------------
class S{public: template<typename T, int N> operator T[N]&(); //我是转型运算符,转换成T[N]类型};void f(int(&)[20]); //参数的类型是20个元的数组void g(S s){ f(s);}
我们试图把S转换成int(&)[20];因此类型A是int(&)[20], 类型P为T[N],于是用int替换T,用20替换N。演绎类型就是成功的。
五、再谈灵活的替换规则
(1)对于模板参数-实参对(P, A),有两种情况下,P可以比A多一个const或volatile限定符。
①原来声明的参数是一个引用参数子,那么P类型可以比A类型多出一个const或volatile
②原来的声明参数是不是一个引用参数子没关系,A类型是指针或成员指针类型,那么P类型也可以比A类型多出一个const或volatile。
(2)当演绎过程不涉及转型运算符模板时,被替换的P类型可以是A类型的基类,或者是当A是指针类型时,P可以是一个指针类型,P指向的类型是A所指向类型的基类, 只有在不精确匹配情况下才会出现这中宽松匹配。
template<typename T>class B{};template<typename T>class D:public B<T>{};template<typename T>void f(B<T>*);void g(D<long> dl){ f(&dl); //成功,用long替换T}
六、警告:类模板参数不能用于实参演绎
七、警告:函数模板的缺省实参不能用于实参演绎,即使实参不是依赖型的实参
template<typename T>void f(T x = 42){}int main(){ f<int>(); //正确,实例化 f();//错误,不能用于实参演绎}
八、一个小技巧 Barton-Nackman方法
当时这种方法被创建出来基于以下几个原因:
(1)当时函数模板不能被重载
(2)运算符==如果重载在类模板里面那么,根据上面的那些灵活的转换方式(指向基类,指向子类之云云),第一个实参(this指针指向),第二个实参的转型规则可能不一样。
现在定义一个模板类Object,那如果要定义这个类的operator ==,那么这个operator==不能定义在类内部(根据(2)),
也不能定义在全局或类之外的命名空间,如template<typename T> bool operator ==(Array<T> const& a, Array<T> const& b){……},(根据(1))
Barton和Nackman将这个运算符作为类的普通友元函数定义在类的内部。如下
#include <iostream>using namespace std;template<typename T>class Object{ public: int a; Object(int n):a(n){} friend bool operator == (Object<T> const<, Object<T> const& rt) { return equal(lt, rt); //根据参数类型调用重载的函数 }};bool equal(Object<int> const& lt, Object<int> const& rt) //这是一个普通函数,可以被随便重载{ return lt.a == rt.a;}int main(){ Object<int> s(1); Object<int> s1(1); cout << (s == s1) << endl; return 0;}
最后顺利编译通过,运行成功。
注:这些代码的运行环境都是在mingw下进行的,VS2013估计自己重新实现了模板名字查找,很多书上说名称找不到的情况,VS2013都找得到(-__-)b,所以为了更好的学习《C++Templates》转投MinGW,编辑器是codeblocks。
编辑整理:Claruarius,转载请注明出处。
模板实参演绎
声明:以上内容来自用户投稿及互联网公开渠道收集整理发布,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任,若内容有误或涉及侵权可进行投诉: 投诉/举报 工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。