为了理解如何从函数调用进行类型推断,考虑下面的例子:
template <typename T> void f(T &p);
其中函数参数p是一个模板类型参数T的引用,非常重要的是记住两点:编译器会引用正常的引用绑定规则;const是底层的,不是顶层的。
从左值引用函数参数推断类型
当一个函数参数是模板类型参数的一个普通(左值)引用时(即,形如T&),绑定规则告诉我们,只能传递给他一个左值(如,一个变量或一个返回引用类型的表达式)。实参可以是const,也可以不是。如果实参是const,则T将被推断为const类型。
template <typename T> void f1(T&); //实参必须是一个左值 //对f1的调用使用实参所引用的类型作为模板参数类型 f1(i); //i是一个int;模板参数类型T是int f1(ci); //ci是一个const int;模板参数T是const int f1(5); //错误:传递给一个&参数的实参必须是一个左值
如果一个函数的参数是const T&,正常的绑定规则告诉我们可以传递给它任意类型的实参------一个对象(const或非const)、一个临时对象或一个字面常量值。当函数参数本身是const时,T的推断的结果不会是一个const类型。const已经是函数参数类型的一部分;因此,它不会是模板参数的一部分:
template <typename T> void f2(const T&); //可以接受一个右值 //f2中的参数是const &;实参中的const是无关的 //在每个调用中,f2的参数都被推断为const int& f2(i); //i是一个int;模板参数T是int f2(ci); //ci是一个const int,但模板参数T是int f2(5); //一个const int&参数可以绑定到一个右值;T是int
从右值引用函数参数推断类型
当一个函数参数是右值引用时(即,形如T&&),正常的绑定规则告诉我们可以传递给它一个右值。当我们这样做时,类型推断过程类似普通左值引用函数参数的推断过程。推断出的T类型是该右值的实参类型:
template <typename T> void f3(T&&); f3(42); //实参是一个int类型的右值;模板参数T是int
引用折叠和右值引用参数
假定i是一个Int对象,我们可能认为像f3(i)这样的调用是不合法的。毕竟,i是一个左值,而通常我们不能将一个右值引用绑定到一个左值上。但是,C++语言在正常绑定规则之外定义了两个例外规则,允许这种绑定,这两个例外规则是move这种标准库设施正确工作的基础。
第一个例外规则影响右值引用参数的推断如何进行。当我们将一个左值(如i)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。因此,当我们调用f3(i)时,编译器推断T的类型为int&,而非int。
T被推断为int&看起来好像意味着f3的函数参数应该是一个类型int&的右值引用。通常,我们不能(直接)定义一个引用的引用。但是,通过类型别名或通过模板类型参数间接定义是可以的。
在这种情况下,我们可以使用第二个例外绑定规则:如果我们间接创建一个引用的引用,则这些引用形成了“折叠”。在所有情况下(除了一个例外)