模板与泛型(二)


模板实参的推断和引用

函数模板的形参可以为左值引用或者是右值引用,如果左值引用,又可分为普通的T&const T&,其参数推断规则如下

左值引用参数推断

当一个函数参数是一个普通的左值引用时T&,其传递的实参只能是一个变量或者是一个返回引用类型的表达式。实参可以是const类型也可以不是,如果是const的,则T被推断为const类型

template<typename T>
void f1(T& op); //实参必须是一个左值
f1(i); //i是一个int;T是int
f1(ci); //ci是一个const int,则T是const int
f1(5); //错误,5是一个右值

如果模板参数是const T&,则它可以接受任何类型的实参(const或非const的对象,临时变量和字面常量)。当实参是const类型时,T不会被推断为const类型,因为const已经是模板参数类型的一部分了

template<typename T>
void f2(const T& op); //可以接受右值
//f2的模板参数是const T&, 实参的const是无效的
//在下面每个调用中,f2的参数都被推断为const int&
f1(i); //i是一个int;T是int
f1(ci); //ci是一个const int,则T是int
f1(5); //const T&可以绑定右值,T是int

右值引用参数推断和引用折叠

同理当一个模板参数声明为右值引用时,它将接受一个右值作为实参

template<typename T>
void f3(T&&);
f3(42); //实参是一个int型右值,T是int

C++编译器对T&&做了一些特殊的设定,具体来说有两点,第一点是如果实参是一个左值,按照上面定义,它是不能直接绑定到右值引用(T&&)上的,但实际上却可以。假定i是一个int对象,当我们调用f3(i)时,编译器会推断T的类型为int&,而非int,此时f3接受的参数变成了一个左值引用的右值引用。通常,我们是不能定义一个引用的引用的,但是编译器为我们提供了第二条特殊设定,即如果我们间接创建了一个引用的引用,那么这些引用形成了折叠。在所有情况下(除了一个例外),引用会折叠成一个普通的左值引用。在新标准中,折叠也可产生一个右值引用,这种情况只能发生在右值引用的引用。即,对于一个给定类型X:

  • X& &, X& &&X&& &都折叠成X&
  • X&& &&折叠成X&&

这两个规则导致下面几个的结论,对于template<typename T> void f3(T&&)这样的模板

  • 如果传递的参数是左值,T推导的结果是左值引用;如果传递过去的参数是右值,T推导的结果是参数类型本身;如果传递的参数是右值引用,则T推导的结果是右值引用
  • 如果T是左值引用,那么T&&的结果仍然是左值引用,即T& &&折叠成了T&
  • 如果T是右值引用,那么T&&的结果仍然是右值引用,即T&& &&折叠成了T&&
  • 如果T是一个右值,那么T&&的结果就是一右值应用

T&& 的作用主要是保持值类别进行转发,它有个名字就叫“转发引用”(forwarding reference)。因为既可以是左值引用,也可以是右值引用,它也曾经被叫做“万能引用”(universal reference)。

理解std::move