模板實參推斷和引用以及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的左值屬性。