C++11 模板參數(shù)的“右值引用”是轉(zhuǎn)發(fā)引用嗎
在C++11中,&&不再只有邏輯與的含義,還可能是右值引用:
void f(int&& i);
但也不盡然,&&還可能是轉(zhuǎn)發(fā)引用:
template<typename T> void g(T&& obj);
“轉(zhuǎn)發(fā)引用”(forwarding reference)舊稱“通用引用”(universal reference),它的“通用”之處在于你可以拿一個左值綁定給轉(zhuǎn)發(fā)引用,但不能給右值引用:
void f(int&& i) { } template<typename T> void g(T&& obj) { } int main() { int n = 2; f(1); // f(n); // error g(1); g(n); }
一個函數(shù)的參數(shù)要想成為轉(zhuǎn)發(fā)引用,必須滿足:
- 參數(shù)類型為T&&,沒有const或volatile;
- T必須是該函數(shù)的模板參數(shù)。
換言之,以下函數(shù)的參數(shù)都不是轉(zhuǎn)發(fā)引用:
template<typename T> void f(const T&&); template<typename T> void g(typename std::remove_reference<T>&&); template<typename T> class A { template<typename U> void h(T&&, const U&); };
另一種情況是auto&&變量也可以成為轉(zhuǎn)發(fā)引用:
auto&& vec = foo();
所以寫范圍for循環(huán)的最好方法是用auto&&:
std::vector<int> vec; for (auto&& i : vec) { // ... }
有一個例外,當(dāng)auto&&右邊是初始化列表,如auto&& l = {1, 2, 3};時,該變量為std::initializer_list<int>&&類型。
轉(zhuǎn)發(fā)引用,是用來轉(zhuǎn)發(fā)的。只有當(dāng)你的意圖是轉(zhuǎn)發(fā)參數(shù)時,才寫轉(zhuǎn)發(fā)引用T&&,否則最好把const T&和T&&寫成重載(如果需要的話還可以寫T&,還有不常用的const T&&;其中T是具體類型而非模板參數(shù))。
轉(zhuǎn)發(fā)一個轉(zhuǎn)發(fā)引用需要用std::forward,定義在<utility>中:
調(diào)用g有幾種可能的參數(shù):
- int i = 1; g(i);,T為int&,調(diào)用g(int&);
- const int j = 2; g(j);,T為const int&,調(diào)用g(const int&);
- int k = 3; g(std::move(k));或g(4);,T為int(不是int&&哦?。{(diào)用g(int&&)。
你也許會疑惑,為什么std::move不需要<T>而std::forward需要呢?這得從std::forward的簽名說起:
template<typename T> constexpr T&& forward(std::remove_reference_t<T>&) noexcept; template<typename T> constexpr T&& forward(std::remove_reference_t<T>&&) noexcept;
調(diào)用std::forward時,編譯器無法根據(jù)std::remove_reference_t<T>反推出T,從而實例化函數(shù)模板,因此<T>需要手動指明。
但是這并沒有從根本上回答問題,或者可以進(jìn)一步引出新的問題——為什么std::forward的參數(shù)不定義成T&&呢?
原因很簡單,T&&會把T&、const T&、T&&和const T&&(以及對應(yīng)的volatile)都吃掉,有了T&&以后,再寫T&也沒用。
且慢,T&&參數(shù)在傳入函數(shù)是會匹配到T&&嗎?
#include <iostream> #include <utility> void foo(int&) { std::cout << "int&" << std::endl; } void foo(const int&) { std::cout << "const int&" << std::endl; } void foo(int&&) { std::cout << "int&&" << std::endl; } void bar(int&& i) { foo(i); } int main() { int i; bar(std::move(i)); }
不會!程序輸出int&。在函數(shù)bar中,i是一個左值,其類型為int的右值引用。更直接一點,它有名字,所以它是左值。
因此,如果std::forward沒有手動指定的模板參數(shù),它將不能區(qū)分T&和T&&——那將是“糟糕轉(zhuǎn)發(fā)”,而不是“完美轉(zhuǎn)發(fā)”了。
最后分析一下std::forward的實現(xiàn),以下代碼來自libstdc++:
template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type& __t) noexcept { return static_cast<_Tp&&>(__t); } template<typename _Tp> constexpr _Tp&& forward(typename std::remove_reference<_Tp>::type&& __t) noexcept { static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument" " substituting _Tp is an lvalue reference type"); return static_cast<_Tp&&>(__t); }
- 當(dāng)轉(zhuǎn)發(fā)引用T&& obj綁定左值int&時,匹配第一個重載,_Tp即T為int&,返回類型_Tp&&為int&(引用折疊:& &、& &&、&& &都折疊為&,只有&& &&折疊為&&);
- const int&同理;
- 當(dāng)轉(zhuǎn)發(fā)引用綁定右值int&&時,匹配第二個重載,_Tp為int,返回類型為int&&;
- const int&&同理。
綜上,std::forward能完美轉(zhuǎn)發(fā)。
程序員總是要在Stack Overflow上撞撞墻才能學(xué)會一點東西。
到此這篇關(guān)于C++11 模板參數(shù)的“右值引用”是轉(zhuǎn)發(fā)引用嗎的文章就介紹到這了,更多相關(guān)C++11 右值引用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語言初識動態(tài)內(nèi)存管理malloc calloc realloc free函數(shù)
動態(tài)內(nèi)存是相對靜態(tài)內(nèi)存而言的。所謂動態(tài)和靜態(tài)就是指內(nèi)存的分配方式。動態(tài)內(nèi)存是指在堆上分配的內(nèi)存,而靜態(tài)內(nèi)存是指在棧上分配的內(nèi)存2022-03-03C++實現(xiàn)讀入二進(jìn)制數(shù)并轉(zhuǎn)換為十進(jìn)制輸出
本文給大家介紹的是一則使用C++實現(xiàn)讀入二進(jìn)制數(shù)并轉(zhuǎn)換為十進(jìn)制輸出的代碼,實現(xiàn)起來其實非常簡單,C++本身就提供了二進(jìn)制類庫的,大家看代碼吧,簡單又實用。2015-03-03C++實現(xiàn)圖片轉(zhuǎn)base64的示例代碼
Base64就是一種 基于64個可打印字符來表示二進(jìn)制數(shù)據(jù)的表示方法,本文主要為大家詳細(xì)介紹了如何使用C++實現(xiàn)圖片轉(zhuǎn)base64,需要的可以參考下2024-04-04VS Code C/C++環(huán)境配置教程(無法打開源文件“xxxxxx.h”或者檢測到 #include 錯誤,請更新in
這篇文章主要介紹了VS Code C/C++環(huán)境配置教程(無法打開源文件“xxxxxx.h” 或者 檢測到 #include 錯誤。請更新includePath) (POSIX API),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-08-08