C++ Template自变量推导(11)---《C++ Templates》

如果我们在一个function template调用语句中明确指定template arguments,会使代码变得笨拙而又不简洁,C++在设计时候考虑到了这种情况,提供了自变量推导的方法,便于我们书写代码,下面我们针对Template自变量推导机制进行必要的讨论。

推导过程

编译器会比对“调用语句内某自变量的类型”和“function template中对应的template parameters类型”,并试图总结出“被推导的一或者多个参数”的正确替换物。每一对“自变量-参数”独立分析,如果推导结束后两者结果有差异,则推导失败。参考如下代码:

template <typename T>
T const& max(T const& a,T const& b){
    return a<b?b:a;
}
int g=max(1,1.0);

显然出错,因为T首先被推导为int,其次又被推导为double,出现歧义,因此推导失败。
即使所有的template parameters推导结果前后一致,如果以自变量替换进去后导致声明的其他部分非法,推导过程仍旧失败,如返回值不匹配,参考如下代码:

template <typename T>
typename T::ElementT at(T const& a,int i){
    return a[i];
}
void f(int *p){
    int x=at(p,7);
}

这里T被退到为int*,然而却推导失败,主要在于返回类型T::ElementT非法。

我们还需要探索“自变量-参数”匹配是如何进行的,因此我们声明A为template arguments,P为template parameters,如果参数被声明为by reference传递,P表示reference类型,A为自变量类型;否则P表示声明之参数类型,A是个由array或者function退化而成并去除外围const和volatile饰词的pointers指针类型,如:

template <typename T>
void f(T);
template <typename T>
void g(T&);

double x[20];
int const seven=7;

f(x);//nonrefernce parameter:T为double
g(x);//reference parameter:T为double[20]
f(seven);//nonreference parameter:T为int
g(seven);//reference parameter:T为int const
f(7);//nonreference parameter,T为int
g(7);//ERROR:不能将7当做int&传递

当字符串字面常数类型的自变量被绑定至reference parameters时,不会发生退化,参考如下代码:

template <typename T>
T const& max(T const& a,T const& b);
max("Apple","Pear");//ERROR

在上面代码中我们针对两种string类型的参数调用max时候,T并不会退化为char const*,奇怪的是“Apple”类型是char const[6],而“Pear”类型被推导为char const[5]。array至pointer类型的退化并没有发生,因为max的参数使用by-reference方式传递参数。

推导之前后脉络

复杂参数化类型的推导,可以参考如下例子进行简单了解:

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*));pointer-to-member type

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
}

复杂的类型声明是由基本构件(pointer、reference、array、function声明符号、pointer-to-member声明符号、template-id等等)构件而成,匹配过程从上层构件开始,在各组成元素中递归进行。公正地说,大多数类型声明构件都可以使用这种方式进行匹配,这些构件被称为deduced contexts。但是相应的有些构件并不是deduced contexts:
受饰类型名称。例如Q<T>::X不会被用以推导template parameter T。例如S

template <int N>
class X{
public:
    typedef int I;
    void f(int){
    }
};
template <int N>
void fppm(void (X<N>::*p) (typename X<N>::I));
int main(){
    fppm(&X<33>::f);//OK,N被推导为33
    return 0;
}

在function template fppm()中,子构件X<N>::I是个nondeduced context。然而pointer-to-member类型中的X<N>却是个deducible context,而且当由此导出的参数N被塞入nondeduced context时,我们将获得一个与实元&X<<33>33>::f相容的类型。于是“自变量-参数”推导成功。
对一个完全伊deduced contexts构建而成的参数类型进行推导,反而导致矛盾结果,参看如下代码,前提是class templates X和Y都已被正确声明:

template <typename T>
void f(X<Y<T>,Y<T> >);

void g(){
    f(X<Y<int>,Y<int> >());//OK
    f(X<Y<int>,Y<char> >());//ERROR
}

第二个function template f()的调用语句的问题在于,根据两个自变量推导出不同的template parameter T,这是不合法的。上述两种情况中,call arguments都是由class template X的default都早函数建立的一个咱叔对象。

特殊推导情境

有两种情形使得(A,P)并非由function template的调用自变量和function template的参数获得。
1)对function template进行取址时,这时P是function template声明语句中的参数化类型,A是被初始化或者被赋值之指针的函数类型。例如:

template <typename T>
void f(T,T);

void (*pf)(char,char)=&f;

这个例子中,p是void(T,T),A是void(char,char),推导成功,并将T替换为char,pf被初始化为f<char>特化体的地址。
2)conversion operator templates(转型运算符模板):

class S{
public:
    template<typename T,int N> operator T&();
};

这时(P,A)的获得犹如涉及自变量及某参数类型,下面例子展示了上述情况的一个变异:

void f(int(&)[20]);
void g(S s){
    f(s);
}

这里我们把s转型为int(&)[20]。因此类型A为int[20],类型P为T。推导成功,T被替换为int[20]。

可接受的自变量转型

通常,template的推导机制会视试图寻找function template parameters的替换品,使参数化类型P与类型A完全相同。然而当它无法达到这个目标时,推导机制容许以下差异:
如果原始函数使用by-reference传递方式,责备替换类型P被类型A可以多带上const饰词或volatile饰词;
如果类型A是一个pointer类型或者pointer-to-member类型,A可以通过一个const饰词或volatile饰词,转型为替换类型P;
除非推导程序因conversion operator template而发生,否则被替换类型P可以使类型A的base class类型,或者A是个pointer-to-X类型时,P可以是个pointer-to-base-class-of-X,参看如下代码:
PS:derived-to-base不能适用于nontype template parameters

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);//OK
}

当推导机制无法进行完全匹配时,才会考虑这种宽松的匹配条件。即使如此,唯有当推导机制找到唯一一个(使类型A复合被替换类型P)的替换方式,推导才会成功。

Class Template Parameters(类别模板参数)
Template argument deduction专门只用于function template和member function template中,更名却的说,class template的自变量不从外界对其中某一构造函数的call arguments中推导出来。

template <typename T>
class S{
    public:
        S(T b):a(b){
        }
private:
    T a;
};
S x(12);//ERROR:class template parameter T从来不从构造函数中被推导出来

预设的调用自变量(Default Call Arguments)

在function template中你也可以指定预设的函数调用自变量,就像在常规函数中一样:

template <typename T>
void init(T* loc,T const& val=T()){
    *loc=val;
}

事实上,正如这个例子所战术的,预设的function call argument也可以依赖某个template parameter。这种受控预设自变量只在调用段没有明确自变量时才被实例化。正是这个法则使得下面的例子合法:

class S{
public:
    S(int,int);
};
S s(0,0);
int main(){
    init(&s,S(7,42));//T=S时T()不合法,但由于给定了一个明确自变量,因此不需将预设自变量T()实例化
    return 0;
}

即使预设的function call arguments并非受控的,它也无法被用来推导template arguments。所以下面的例子不合法:

template <typename T>
void f(T x=42){
}
int main(){
    f<int>();
    f();//ERROR:无法从预设的call argument中推导T
    return 0;
}

Barton-Nackman Trick

由于以前function template无法重载,所伊针对需要重载的成员函数,Barton和Nackman解决该问题的方法是:把这些需要重载的函数声明为class内部的一个普通的friend函数,举例如下:

template <typename T>
class Array{
public:
    ...
    friend bool operator==(Array<T> const&a,Array<T> const&b){
        return ArrayAreEqual(a,b);
    }
};

函数本身不是function template的局纤体,只是个普通的non-template function,因具现过程影响而插入global作用域中,由于是个non-template function,可以和其他重载形式的operator==共存。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值