演绎的过程
关于模板演绎的一个示例:
#include<iostream>
using namespace std;
template<typename T>
typename T::type FUN(T* t)
{
return t[5];
}
void main()
{
int * p=0;
int x = FUN(p);
}
上面的示例是错误的,错误原因为:
1 error C2893: 未能使函数模板“T::type FUN(T *)”专用化 (补充:找不到可以满足的模板进行实例化)
2 IntelliSense: 没有与参数列表匹配的 函数模板 "FUN" 实例
参数类型为: (int *)
因为函数模板的返回类型T::type,只有T类型提供了成员类型type的时候,此函数模板才能被匹配成功,显然int *不能将此模板特化,然后FUN(p)就在别处找满足模板实参int*类型的FUN模板,最终没有找到,于是报错——找不到可以通过实参演绎进行特化(这里的特化指的是实例化,和全局特化/局部特化不是同一个概念,此文中后续的"特化"一般都是这个意思)的模板!
由此可以看出实参演绎的特化过程是:找那些能满足实参演绎的模板进行特化,如果没有满足的模板,则报告找不到!
我们再看一个示例和上面的示例作对比,看看显式特化是怎么实例化模板的:
#include<iostream>
using namespace std;
template<typename T>
typename T::type FUN(T* t)
{
return t[5];
}
void main()
{
int * p=0;
int x = FUN<int *>(p);
}
上述示例的错误原因为:
1 error C2770: “T::type FUN(T *)”的显式 模板 参数无效
2 IntelliSense: 没有与参数列表匹配的 函数模板 "FUN" 实例
参数类型为: (int *)
从这里可以一窥显式特化和演绎之间的不同了:显式特化是对现存最可以满足的模板进行特化,如果不能对此模板进行特化(模板参数不匹配),则报模板实参无效的错误!
演绎、decay、&
decay(数组或函数到指针的转型)
一个示例:
#include<iostream>
using namespace std;
template<typename T>
void f(T);
template<typename T>
void g(T &);
template<typename T>
void h(T const &, T const & );
double x[20];
int const seven = 7;
void main()
{
f(x);//发生decay
g(x);//引用不会decay
f(seven);//去顶层const
g(seven);//不会发生去顶层const
f(7);//此时将去掉顶层cont——7的类型为int const
//g(7);//error:int &和int const &不匹配(其实是无法找到可以支持实参演绎的模板)
h("aa","bb");
//h("aa", "bbb");//error:char const [2]和char const [3]不是同一模板实参
}
由示例总结两条演绎规则:
1,函数模参是引用:不会去掉实参的顶层const类型、不会发生decay
2,函数模参是值:会去掉实参的顶层const类型、会发生decay
(一般参数传递如果函数形参类型不是pointer type或者reference type,形参对于实参的顶层const特性一般都会去掉,原因:如果是值拷贝,那么形参有一个和实参完全独立的拷贝,此时形参的修改不会影响到实参!但是如果是底层const的话不会去掉,原因很simple:一般只有指针和引用具有底层const(引用的底层const是默认的),以指针或者引用传递参数,将受到客户端对所引用内存的访问权的强制指定!)
演绎上下文
一个稍显复杂的例子:
#include<iostream>
#include<string>
using namespace std;
class X{
public:
string(&fun(double(& arr)[10]))[5]{
int i = 0;
for (auto & elem : arr)
elem=++i;
static string arrString[5] = {"a","b","c","d","e"};
return arrString;
}
};
class C{
public:
double(&fun(string(&arr)[10]))[5]{
int i = 0;
string str = "number-";
for (auto & elem : arr)
elem = str + to_string(++i);
static double arrDouble[5] = { 1,2,3,4,5 };
return arrDouble;
}
};
template<int N,int M, typename T1, typename T2, typename T3>
void FUN(T1(&(T2:: *pf)(T3(&)[M]))[N],T2 & object){
static T3 arr[10];
int i = 0;
for (auto & elem : (object.*pf)(arr)){
cout << arr[i++] << " "<<elem << " " << endl;
}
}
void main(){
X x;
FUN(&X::fun,x);
C c;
FUN(&C::fun, c);
}
FUN<5>(&X::fun)这句话中用来演绎的实参的类型是:类X中的一个函数指针,函数的返回类型是数组,函数的参数类型是数组,这个类型包括这几部分:类X基本类型,类X的成员指针类型、函数类型、两个数组类型,匹配模板实参的过程是:从最顶层的构造开始,不断递归各种组成元素!例如:先演绎出X类型,再演绎出X的成员函数指针类型、再演绎出数组类型。。。
演绎的上下文:类型声明构造(例如:类X基本类型,类X的成员指针类型、函数类型、两个数组类型)
哪些演绎上下文不能用来演绎:
1,受限的类型名称:例如:X<T>::type其中type是受限的类型名!
例子:
#include<iostream>
#include<string>
using namespace std;
template<typename T>
class X
{
public:
typedef int type;
};
template<typename T>
void FUN(typename X<T>::type i)
{
cout << i << endl;
}
void main()
{
//演绎
//FUN(5);//error
//显式特化
FUN<int>(5);
}
失败的原因在于,5是一个int类型,而要靠int类型来演绎出X<T>::type中的T明显不可能!(况且type根本就是一个未知物——局部特化可以使其不存在)
——这种错误属于由一个基本类型推断出一个泛化类型!——Compiler will say NO!
2,除了非类型参数以外,模板参数还包括其他成分的非类型表达式:例如:X<N+1>
#include<iostream>
#include<string>
using namespace std;
template<int N>
class X
{
public:
typedef int type;
};
template<int N>
void FUN(X<N +1>);
void main()
{
FUN(X<4>());
}
错误原因:
1 error C2783: “void FUN(X<N+1>)”: 未能为“N”推导 模板 参数
假设上例能成功,X<4>类型和X<N+1>类型匹配,最终两者类型必须都是X<4>(因为形参实参类型相匹配才能演绎),那么按理N就会是3,但问题是:编译器怎么知道N的类型要4的基础上减去1呢?编译器无法知道:因为<>内是N+1,所以最后的N就是N+1-1!(虽然我们知道!)
演绎过程不唯一——怎样演绎出受限的类型名称!
一个例子:
#include<iostream>
#include<string>
using namespace std;
template<int N>
class X
{
public:
typedef int type;
void f(int){};
};
template<int N>
void fppm(void(X<N>:: *)(typename X<N>::type));
void main()
{
fppm(&X<33>::f);
}
示意图:
总结一句话:低级演绎上下文的模板参数在高级演绎上下文中已经被推倒出来!
另外,我们试图从低演绎上下文中推断出高演绎上下文,结果失败!
#include<iostream>
#include<string>
using namespace std;
template<int N>
class X;
template<int N>
class C
{
public:
void f(C<N>);
};
template<int N>
class X
{
public:
typedef C<N> type;
};
template<int N>
void fppm(void (typename X<N>::type:: *)(C<N>));
//void(X<4>::type:: )(C<4>)
void main()
{
fppm(&(X<4>::type::f));
}
其中C<N>属于低级演绎上下文,X<N>::type属于高级演绎上下文,X<N>::type的N想通过C<N>演绎出来结果失败!
两种特殊的演绎:
1,赋值时演绎
#include<iostream>
#include<string>
using namespace std;
template<typename T>
void f(T, T);
void(*pf)(char, char) = &f;
void main()
{
}
此处void(*pf)(char, char) = &f需要一个void()(char, char)类型的函数,所以模板的T被演绎出了char!
2,转型时演绎
#include<iostream>
#include<string>
using namespace std;
class S
{
public:
template<typename T>
operator T&();
};
void f(int &);
void main()
{
S s;
f(s);
}
此例中s对象在匹配f函数参数的时候,将自己转型为in&,此时发生了演绎以支持转型操作!
演绎中几种可接受的类型转型
1,当模板声明参数是引用参数子
#include<iostream>
using namespace std;
template<typename T>
void FUN(T & a)
{
++a;
}
void main()
{
int a = 0;
FUN(a);
cout <<a << endl;
}
2,当模板实参是指针,那么模板声明参数可以加const/volatile(只能是顶层const支持转型)
#include<iostream>
using namespace std;
//顶层const可以
template<typename T>
void FUN(T * const a)
{
++(*a);
}
//底层const不行
template<typename T>
void FUN(const T * a)
{
a = 0x11;
}
//用于验证对于底层const普通参数转型可以但是泛型转型不支持
void fun(const int const * a)
{
}
void main()
{
int a = 0;
FUN(&a);
fun(&a);
cout << a << endl;
}
3,派生类的引用、指针可以向基类转型
#include<iostream>
using namespace std;
template<typename T>
class Base
{
};
template<typename T>
class Drived :public Base<T>
{
};
template<typename T>
void FUN(Base<T> *);
template<typename T>
void FUN(Base<T> &);
void main()
{
FUN(&Drived<int>());
FUN(Drived<int>());
}
注意:上面必须提供类模板的定义(涉及到转型、创建对象必须看到类的定义!具体细节这篇文章开头就有论述《模板的实例化剖析》)
演绎不适用于类模板!
#include<iostream>
using namespace std;
template<typename T>
class X
{
public:
X(T b) :a(b){}
private:
T a;
};
void main()
{
X(10);
}
上面示例想通过X的构造函数演绎模板参数T,失败!
演绎与缺省调用实参
——可以有缺省调用实参,并且缺省调用实参可以依赖于模板参数
#include<iostream>
using namespace std;
class X
{
public:
X(int, int);//1
};
template<typename T>
void FUN(T a=T()){//2
}
void main()
{
FUN(X(2,2));
}
编译器在编译模板的时候,有这样一条策略:假定缺省实参不会被使用(对于该函数缺省实参可能会导致的语义错误,编译器假定该缺省实参不会被用到,可在本文中详细了解:《模板的实例化剖析 》)(上面示例中的1、2位置可以验证这条策略!)
不能靠缺省调用实参演绎模板参数
#include<iostream>
using namespace std;
template<typename T>
void FUN(T a=2){//2
}
void main()
{
//FUN();//error
FUN(3);
}
上例试图使用3缺省调用实参演绎T,但是错误!
Barton-Nackman方法
Barton-Nackman面临的问题:想为两个类模板定义自己的operator ==,但是在不支持函数模板重载的当时,不能定义两个函数名都是operator==但是参数不一样的函数模板
Barton-Nackman的解决办法:利用边缘实例化、ADL查找、友元
示例:
#include<iostream>
using namespace std;
//用于处理X的成员是否相等的函数
template<typename T>
bool ProcessForX(T & t1,T & t2){
return t1.GetFirst() == t2.GetFirst()&& t1.GetSecond() == t2.GetSecond();
}
template<typename T>
class X
{
public:
X(T t1, T t2) :a(t1), b(t2){}
//限制的模板扩展
friend bool operator == (X<T> & x1,X<T> & x2){
return ProcessForX(x1, x2);
}
T const & GetFirst(){return a;}
T const & GetSecond(){return b;}
private:
T a;
T b;
};
void main()
{
X<int> x1(2, 2);
X<int> x2(2, 2);
if (x1 == x2)
cout << "x1==x2" << endl;
else
cout << "x1!=x2" << endl;
}
特点:
1,此时友元函数operator == 不是函数模板,可以被重载
2,operator == 是内联函数,等于直接调用ProcessForX(ProcessForX是X类模板相等的外部实现)
缺点:
上述友元函数operator ==被在类模板中声明了,该友元函数在外部可见与否要视情况而定(是否发生ADL,详情在《模板的实例化剖析 》)
缺点示例:
#include<iostream>
using namespace std;
//用于处理X的成员是否相等的函数
template<typename T>
bool ProcessForX(T & t1,T & t2){
return t1.GetA() == t2.GetA();
}
class C{
};
template<typename T>
class X
{
public:
X(T t1) :a(t1){}
//限制的模板扩展
friend bool operator == (X<T> & x1,X<T> & x2){
return ProcessForX(x1, x2);
}
T const & GetA(){return a;}
private:
T a;
};
void main()
{
C c1,c2;
X<C> x1(c1);
X<C> x2(c2);
if (x1 == x2)
cout << "x1==x2" << endl;
else
cout << "x1!=x2" << endl;
/*
错误 1 error C2678: 二进制“==”: 没有找到接受“const C”类型的左操作数的运算符(或没有可接受的转换)
*/
}
只有当operator == 函数参数是和X类相关的时候,才能在X内的内部找到operator == 函数,但是C类型的参数不是和X相关(你可能以为C可以转型为X所以C和X还是相关的,但是如果这也算相关,那么还有什么类型不是和X相关呢?),所以根本找不到operator == 函数(虽然C类型可以匹配operator == 的形参X<C> & 类型)!
Barton-Nackman面临问题的现今解决之道(利用函数模板重载):
#include<iostream>
#include<vector>
#include<list>
using namespace std;
//为两个类模板设定的==操作符
template<typename T>
bool operator==(vector<T> & v1, vector<T> & v2)
{
cout << "the operator == for vector!" << endl;
return true;
}
template<typename T>
bool operator==(list<T> & l1, list<T> & l2)
{
cout << "the operator == for list!" << endl;
return true;
}
void main()
{
vector<int> v1{ 1, 2, 3 };
vector<int> v2{ 1, 2, 3 };
list<int>l1{ 1, 2, 3 };
list<int>l2{ 1, 2, 3 };
v1 == v2;
l1 == l2;
/*
输出:
the operator == for vector!
the operator == for list!
*/
}