模板实参推断和引用以及move和forward详解
从左值引用函数参数推断类型
当一个函数的参数是一个左值引用时,我们只能传递给它一个左值,实参可以是const类型或非const类型,实参是const,则T被推断为const。
例如:
template<typename T> void f1(T&);
f1(i); //实参为int,则T推断为int
f1(ci); //实参为const int,T推断为const int
f1(5); //实参为右值,不能传入右值,错误
如果一个函数参数类型是const T&,正常绑定规则告诉我们可以给它传递任何类型实参–一个对象(const或非const)、一个临时对象或是一个字面常量值。当函数参数本身是const时,T的类型推断的结果不会是一个const类型,因为const已经是函数参数类型的一部分了,它不会再是模板参数类型的一部分。
template<typename T> void f2(const T&);
f2(i); //i是int,则模板参数类型推断为int
f2(ci); //i是const int,模板参数同样被推断为int
f2(5); //一个const &参数可以绑定到右值,则T为int
从右值引用函数参数推断类型
当一个函数参数是一个右值引用时,显然我们可以传递一个右值给它。
template<typename T> void f3(T&&);
f3(42); //实参是一个int型右值,模板参数类型为int
引用折叠和右值引用参数
我们同样可以将一个左值传给一个参数为右值引用的模板函数,此时编译器会推断模板类型参数为实参的左值引用类型,例如当我们调用f3(i)时,此时T被推断为int &,而非int。
此时就会出现引用的引用这种情况,这种情况下,这些引用会形成“折叠”,折叠成左值的引用或者右值的引用。
* X& &、X& &&、X&& &都被折叠成X&
* 类型X&& &&被折叠成X&&
f3(i); //实参是一个左值,模板参数T推断为int&,则参数i类型变为int& &&,由引用折叠得到为int&
f3(ci); //实参是一个左值,模板参数T推断为const int&
编写接受右值引用参数的模板参数
template<typename T> void f3(T &&val)
{
T t = val;
}
- 当我们对一个右值调用f3时,例如f3(43),T被推断为int,此时,局部变量t是int型,是val的一个拷贝。
- 当我们对一个左值调用f3时,T被推断为int &,此时t的类型是int&,t不是val的拷贝,而是val的一个引用。
move
标准库是这样定义move的:
template<typename t>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type&&>(t);
}
当调用std::move(string(“bye”))时,实参为一个右值:
* 推断出T类型为string
* remove_reference<string>::type为string
* 将t强制转换为string&&并返回。
当调用std::move(s)时,s为一个左值:
* 推断出T类型为string&
* 则t为string& &&,折叠成string&
* remove_reference<string&>::type为string
* 转换t为string&&,并返回
forward
除了move()语义之外,右值引用还需要解决的一个问题就是完美转发,转发问题针对的是模板函数,要求我们保持实参类型将其传递给其他函数。
forward原型类似如下:
template<class TYPE>
TYPE&& forward(typename remove_reference<TYPE>::type& arg)
{
return static_cast<TYPE&&>(arg);
}
通常情况下,我们使用forward传递那些定义为模板类型参数的右值引用的函数参数。通过其返回类型上的引用折叠,forward可以保持给定实参的左值/右值属性:
template<typename Type> intermediary(Type &&arg)
{
finalFcn(std::forward<Type>(arg));
}
当传入一个右值参数例如string("ss"):
* Type会被推断为string,调用forward<string>
* forward的返回类型TYPE&&为string&&
* arg为remove_reference<string>::type&也就是string&类型
* 再将arg强制为string&&类型返回,保持了arg的右值属性。
当传入一个左值参数,例如string时:
* Type推断为string&,则arg为string& &&类型,引用折叠得到string&类型。此时调用forward<string&>
* forward返回值为string& &&,折叠为string&
* arg为remove_reference<string&>::type& 为string&类型
* 将arg转为string&返回,保持了arg的左值属性。