对于函数模板,编译器通过隐式推断模板实参。其中,从函数实参来确定模板实参的过程被称为模板实参推断。在模板实参推断过程中,编译器使用函数调用中的实参类型来寻找模板实参,用这些模板实参生成的函数版本与给定的函数调用最为匹配;
类型转换与模板类型参数:
对于模板函数和普通函数而言,在函数调用过程中实参将会用来初始化函数的形参;
在模板函数中,采用特殊的初始化规则(编译器通常不会对实参进行类型转换,而是生成一个新的模板实参)。
- const转换:
可以将一个非const对象的引用(或指针)传递给一个const的引用(或指针)形参 - 数组或函数指针转换:
如果函数形参不是引用类型,则可以对数组或函数类型的实参应用正常的指针转化。 其他类型转换(算术转换、派生类向基类的转换、用户自定义转换)都不能应用于函数模板。
template<typename T> T fobj(T,T);
template<typename T> T fref(const T&,const T&);
string s1("a value");
const string const_s2("another value");
fobj(s1,const_s2);
fref(s1,const_s2);
int a[10],b[43];
fobj(a,b);
fref(a,b);
将实参传递给带模板类型的函数型参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换。
- 使用相同模板参数类型的函数形参:由于模板函数只允许有限的几种类型转换,因此传递给这些形参的实参必须具有相同的类型。
- 正常类型转换应用于普通函数实参:在模板函数形参中,未涉及模板类型参数的类型不进行特殊处理,他们正常转换为对应形参的类型;
template<typename T> ostream &print(ostream& os,const T& obj){
return os >> obj;
}
print(cout,32);
ofstream f("output");
print(f,10);
如果函数参数类型不是模板参数,则对实参进行正常的类型转换
函数模板显式实参
在某些情况下编译器无法判断出模板实参的类型。其他情况下,我们允许用户控制模板实例化。
- 指定显式模板实参
作为一个允许用户指定使用类型的例子,我们将定义一个名为sum的函数模板。它接受两个不同类型的参数。我们希望允许用户指定结果的类型,这样,用户就可以选择合适的精度。
template<typename T1,typename T2,typename T3>
T1 sum(T2,T3);
auto val3 = sum<long long>(i,lng);
显式模板实参按从左至右的顺序与对应的模板参数匹配; 看下面的代码:
template<typename T1,typename T2,typename T3>
T3 sum (T1,T2);
auto val3 = sum<long long,int,long>(i,lng);
- 正常类型转换应用于显式指定的实参:
对于模板类型参数已经指定了的函数实参,可以进行正常的类型转换
尾置返回类型于类型转换
但我们希望用户确定返回类型时,用显式模板实参表示模板函数的返回类型是很有效大的。
但在其他情况下,要求显示指定模板实参会给用户增添额外负担,而且不会带来好处。
例如,我们希望编写一个函数,接受表示序列的一对迭代器和返回类型中一个元素的引用:
template<typename It>
auto fcn(It beg,It end) -> decltype(*beg){
return *beg;
}
std::string s1("abcdefg");
auto ret = fcn(s1.begin(),s1.end());
- 进行类型转换的标准库模板类
有时我们无法直接获取所需要的类型。例如,我们可以希望编写一个类型fcn的函数,但返回一个元素的值而非引用。
template<typename It>
auto fcn2(It beg,It end) ->
typename remove_reference<decltype(*begin)>::type{
return *beg;
}
函数指针与实参推断
当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时,编译器使用指针的类型来推断模板实参
例如,假定我们有一个函数指针,它指向的函数返回int,接受两个参数,每个参数都是指向const int的引用。我们可以使用该指针指向compare的一个实例
template<typename T> int compare(const T&,const T&);
int (*pf1)(const int&,const int&) = compare;
当参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板 参数,能唯一确定其类型或值
模板实参推断和引用
template <typename T> void f(T &p);
函数参数p是一个模板类型参数T的引用,编译器会应用正常的引用绑定规则:const是底层的,不是顶层的
- 从左值引用函数参数推断类型
当一个函数参数是模板类型参数的一个普通(左值)引用时(T&),绑定规则告诉我们,只能传递给它一个左值。实参可以是const类型,也可以不是。如果实参是const的,则T将被推断为const类型(const T&):
template<typename T> void fl(T&);
fl(i);
fl(ci);
fl(5);
如果一个函数参数的类型是const T&,正常的绑定规则告诉我们可以传递给它任何类型的实参—— 一个对象(const or not const )、一个临时对象或是一个字面常量值。
template<typename T> void f2(const T&);
f2(i);
f2(ci);
f2(5);
- 从右值引用函数参数推断类型
template<typename T> void f3(T&&);
当一个函数参数是一个右值引用(T&&)时,正常绑定规则告诉我们可以传递给他一个右值。
-
引用折叠和右值引用参数 通常我们不能将一个右值引用绑定到一个左值上。但是,c++语言在正常绑定规则之外定义了两个例外规则,允许这种绑定
- 第一个例外规则影响右值引用参数的推断如何进行。当我们将一个左值传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&),编译器推断模板类型参数为实参的左值引用类型。 例如,当调用f3(i)时,编译器推断T的类型为int&,而非int。
可以将上述情况理解为定义了一个了类型为int& 的右值引用 ,通常情况下,我们不能(直接)定义一个引用的引用,但是,通过类型别名或通过模板类型参数间接定义是可以的。 - 第二个例外绑定规则:如果我们间接创建一个引用的引用,则这些引用形成了"折叠"。在所有情况下(除了一个例外),引用会折叠成一个普通的左值引用类型。在新标准中,折叠规则扩展到了右值引用。只有在一种情况下引用会折叠成右值引用:右值引用的右值引用。X& &、X& && 和 X&& &都折叠为类型 X& ; 类型X&& && 折叠为X&&
引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数
void f3<int&>( int&);
如果一个函数参数是指向模板类型参数的右值引用,则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数,则函数参数被实例化为一个普通的左值引用。
- 编写接受右值引用参数的模板函数
template<typename T> void f3(T&& val){
T t = val;
t = fcn(t);
if(val == t){...}
}
在实际情况中,右值引用通常用于两种情况:模板转发其实参 或 模板被重载。
-
转发 某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。再此情况下,我们需要保持被转发实参的所有性质,包括实参是否是const的以及实参是左值还是右值。
template <typename F,typename T1,typename T2>
void flip1(F f,T1&& t1,T2&& t2){
f(t2,t1);
}
如果一个函数参数是指向模板类型参数的右值引用,他对应的实参的const属性和左值/右值属性将得到保持;
template<typename F,typename T1,typename T2>
void flip(F f,T1 &&t1,T2 &&t2){
f(std::forward<T2>(t2),std::forward<T1>(t1));
}
当用于一个指向模板参数类型的右值引用函数参数时,forward会保持实参类型的所有细节
参考文献:C++ Primer(第五版) 本文仅为本人学习笔记,仅做记录用途
|