编译器会对函数模板参数进行推导。
从左值引用函数参数推断类型
当一个函数参数是模板参数类型的一个(左值)引用时(即,形如T&),绑定规则告诉我们,只能传递一个左值(如,一个变量或一个返回引用类型的表达式)。实参可以是const类型,也可以不是。如果实参是const的,则T将被推断为const类型:
1 | template<typename T> void f1(T&); // 实参必须是一个左值 |
如果一个函数参数的类型是const T&,正常的绑定规则告诉我们可以传递给它任何类型的实参—一个对象(const 或非const)、一个临时对象或者一个字面常量值。当函数参数本身是const时,T的类型推断的结果不会是一个const类型。const已经是函数参数类型的一部分;因此,它不会是模板参数类型的一部分。
1 | template<typename T>void f2(const T&); //可以接受一个右值 |
从右值引用函数参数推断类型
当一个函数参数是一个右值引用类型(即,形如T&&)时,正常绑定规则告诉我们可以传递给它一个右值。当我们这样做时,类型推断过程类似普通左值引用函数参数的推断过程。推断出的T的类型是该右值实参的类型:
1 | template<typename T> void f3(T&&); |
引用折叠和右值引用参数
假定i是一个int对象,我们可能认为像f3(i)这样的调用是不合法的。毕竟,i是一个左值,而通常我们不能将一个右值引用绑定到一个左值上。但是C++语义在正常绑定规则之外定义了两个例外规则。这两个规则是move这种标准库设施正确工作的基础。
第一个例外规则影响右值引用参数的推断如何进行。当我们将一个左值(如i)传递给函数的右值引用类型参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。因此,当我们调用f3(i)时,编译器推断T的类型为int&,而非int。
T被推断为int&好像意味着f3的函数参数应该是一个类型int&的右值引用。通常,我们不能(直接)定义一个引用的引用。但是,通过类型别名或通过模板类型参数间接定义是可以的。
在这种情况下,我们可以使用第二个绑定规则:如果我们间接创建一个引用的引用,则这些引用形成了折叠。对于一个给定的类型X:
- X& &、X& &&和X&& &都折叠成类型X&
- 类型X&& &&折叠成X&&
如果将引用折叠规则和右值引用的特殊类型推断规则组合在一起,则意味着我们可以对一个左值调用f3。当我们将一个左值传递给f3的(右值引用)函数参数时,编译器推断T为一个左值引用类型。
1 | f3(i) // 实参是一个左值,模板参数T是int& |
当一个模板参数T被推断为引用类型时,折叠规则告诉我们函数参数T&&折叠 为一个左值引用类型。例如,f3(i)的实例化结果可能像下面这样:
1 | // 无效代码,只是用于演示目的 |
f3的函数参数是T&&且T是int&,因此,T&&是int& &&,会折叠成int&。因此,即使f3的函数参数形式是一个右值引用(即T&&),此调用也会用一个左值引用类型(即,int&)实例化f3:
1 | void f3<int&>(int&); // 当T是int&时,函数参数折叠为int& |
这两个规则导致了两个重要结果:
- 如果一个函数参数是一个指向模板实参类型的右值引用,且函数参数被绑定到一个左值;且
- 如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将被实例化为一个(普通)左值引用类型参数。
另外值得注意的是,这两个规则暗示,我们可以将任意类型的实参传递给T&&类型的函数参数。对于这种类型的参数,可以传递给它右值,亦可以传递给它左值。
forward(完美转发)
某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在此情况下,我们需要保持被转发实参的所有性质,包括实参类型是否是const的以及实参是左值还是右值。
编写一个函数,它接受一个可调用表达式和两个额外实参。该函数将调用给定的可调用对象,将两个额外参数逆序传递给它。
1 | // 接受一个可调用对象和另外两个参数的模板 |
这个函数一般情况下工作得很好,但当我们希望用它调用一个接受引用参数的函数时就会出现问题。
1 | void f(int v1, int &v2) { |
filp1调用f时不会改变j,这是因为j是以传值调用的形式传递给flip1的。
定义能保持类型信息的函数参数
为了通过翻转函数传递一个引用,需要重写函数,使其参数能保持给定实参的“左值性”;
通过将一个函数参数定义为一个指向模板类型参数的右值引用,使得我们可以保持const属性,因为在引用类型中的const是底层。
如果一个函数参数是指向模板类型参数的右值引用,它对应的实参的const属性和左值/右值属性将得到保持。