模板实参推断和引用折叠
template <typename T> void f(T &p);
函数参数p是一个模板类型参数T的引用,记住两点:编译器会应用正常的引用绑定规则; const是底层的,不是顶层的.
从左值引用函数参数推断类型
当一个函数参数是模板类型参数的一个普通(左值)引用时(即, T&),绑定规则告诉我们,只能传递给它一个左值(如,一个变量或一个返回引用类型的表达式).实参可以是const,也可以不是.如果实参是const的,则T将被推断为const类型:
template <template T> void f1(T&); // 实参必须是一个左值
// 对f1的调用使用实参所引用的类型作为模板参数类型
f1(i); // i是一个int:T是int
f1(ci); // ci是 const int: T是cosnt int
f1(5); // 错误:不能给右值
如果一个函数参数的类型是const T&,正常的绑定规则告诉我们可以传递给它任何类型的实参:一个对象(const或非const), 一个临时对象或是一个字面常量值.当函数参数本身是cosnt时,T的类型推断结果不会是一个const类型,const是函数参数类型的一部分,而不是T本身:
template <typename T> void f2(const T&); // 可接受右值
// f2中参数是 const &; 与实参中的const无关
// 在每个调用中,f2的函数参数被推断为 const int&
f2(i); // T -> int
f2(ci); // T -> int
f2(5); // 一个 const & 参数可以绑定右值; T -> int
从右值引用函数参数推断类型
当一个函数参数是一个右值引用 (形如T &&) 时,正常绑定规则告诉我们可以传递给它一个右值.与左值引用函数参数的推断类似,推断出的T类型是该右值实参的类型:
template <typename T> void f3(T&&);
f3(42); // T -> int
引用折叠和右值引用参数
i是一个int对象,我们可能认为f3(i)这样的调用是不合法的.毕竟, i是一个左值,而通常我们不能将一个右值引用绑定到一个左值上.但是,有两个例外:
- 第一个例外影响右值引用参数的推断如何进行.当我们将一个左值(如
i)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T &&)时,编译器推断模板类型参数为实参引用类型.因此,当我们调用f3(i)时,T被推断为int&,而非int.T被推断为int&意味着f3的参数类型是一个int&的`右值引用,通常我们不能直接定义一个引用的引用,但是通过类型别名或通过模板类型参数间接定义是可以的. - 第二个例外:如果我们间接创建一个引用的引用,则这些引用形成了"折叠".对于一个给定类型
x:X& &, X& &&和X&& &都折叠成类型X&- 类型
X&& &&折叠成X&&
引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数
如果将引用折叠规则和右值引用的特殊类型推断规则组合在一起,则意味着我们可以对一个左值调用f3.当我们将一个左值传递给f3的(右值引用)函数参数时,编译器推断T为一个左值引用类型:
f3(i); // 实参是一个左值, T -> int&
f3(ci); // 实参是一个左值, T -> const int&
当一个模板参数T被推断为引用类型时,折叠规则告诉我们函数参数T&&折叠为一个右值引用类型.
// 无效代码,用于演示
void f3<int&>(int& &&); // T是int&, 函数参数为int& &&
f3的函数参数是T&&且T是int&,因此T&&是int& &&,会折叠成int&.因此即使f3的函数参数形式是右值引用,此调用也会用一个左值引用类型来实例化f3:
void f3<int&>(int&);
这两个规则导致:
- 如果一个函数参数是指向模板类型的右值引用(
T&&),则它可以被绑定到一个左值上, 且 - 如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将被实例化为一个左值引用参数
T&
这两个规则意味着:我们可以将任意类型的实参传递给T&&类型的函数参数.
编写接受右值引用参数的模板函数
模板参数推断为一个引用类型,会有很多神奇的影响:
template <typename T> void f3(T&& val) {
T t = val; // 拷贝还是引用 ?
t = fcn(t); // 赋值只改变t还是既改变了val ?
if (val == t) {.........} // 若T为引用, 则一直为true
}
当我们对一个右值调用f3时,例如字面常量42,T为int.在此情况下,局部变量t的类型为int,且通过拷贝参数val的值被初始化.当我们对t赋值时,参数val保持不变.
当我们对一个左值i调用f3时,则T为int&.当我们定义并初始化局部变量t时,赋予它类型int&.因此,对t的初始化将其绑定到val.当我们对t赋值时,也同时改变了val的值,在f3的这个实例版本中,if判断永远为真.

浙公网安备 33010602011771号