在C++11中,&&
再也不只有邏輯與的含義,還多是右值引用:ios
void f(int&& i);
但也不盡然,&&
還多是轉發引用:c++
template<typename T> void g(T&& obj);
「轉發引用」(forwarding reference)舊稱「通用引用」(universal reference),它的「通用」之處在於你能夠拿一個左值綁定給轉發引用,但不能給右值引用:程序員
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); }
一個函數的參數要想成爲轉發引用,必須知足:函數
參數類型爲T&&
,沒有const
或volatile
;code
T
必須是該函數的模板參數。rem
換言之,如下函數的參數都不是轉發引用:get
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&&
變量也能夠成爲轉發引用:編譯器
auto&& vec = foo();
因此寫範圍for
循環的最好方法是用auto&&
:it
std::vector<int> vec; for (auto&& i : vec) { // ... }
有一個例外,當auto&&
右邊是初始化列表,如auto&& l = {1, 2, 3};
時,該變量爲std::initializer_list<int>&&
類型。io
轉發引用,是用來轉發的。只有當你的意圖是轉發參數時,才寫轉發引用T&&
,不然最好把const T&
和T&&
寫成重載(若是須要的話還能夠寫T&
,還有不經常使用的const T&&
;其中T
是具體類型而非模板參數)。
轉發一個轉發引用須要用std::forward
,定義在<utility>
中:
#include <utility> template<typename... Args> void f(Args&&... args) { } template<typename T> void g(T&& obj) { f(std::forward<T>(obj)); } template<typename... Args> void h(Args&&... args) { f(std::forward<Args>(args)...); }
調用g
有幾種可能的參數:
int i = 1; g(i);
,T
爲int&
,調用g(int&)
;
const int j = 2; g(j);
,T
爲const int&
,調用g(const int&)
;
int k = 3; g(std::move(k));
或g(4);
,T
爲int
(不是int&&
哦!),調用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;
調用std::forward
時,編譯器沒法根據std::remove_reference_t<T>
反推出T
,從而實例化函數模板,所以<T>
須要手動指明。
可是這並無從根本上回答問題,或者能夠進一步引出新的問題——爲何std::forward
的參數不定義成T&&
呢?
緣由很簡單,T&&
會把T&
、const T&
、T&&
和const T&&
(以及對應的volatile
)都吃掉,有了T&&
之後,再寫T&
也沒用。
且慢,T&&
參數在傳入函數是會匹配到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&
。在函數bar
中,i
是一個左值,其類型爲int
的右值引用。更直接一點,它有名字,因此它是左值。
所以,若是std::forward
沒有手動指定的模板參數,它將不能區分T&
和T&&
——那將是「糟糕轉發」,而不是「完美轉發」了。
最後分析一下std::forward
的實現,如下代碼來自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); }
當轉發引用T&& obj
綁定左值int&
時,匹配第一個重載,_Tp
即T
爲int&
,返回類型_Tp&&
爲int&
(引用摺疊:& &
、& &&
、&& &
都摺疊爲&
,只有&& &&
摺疊爲&&
);
const int&
同理;
當轉發引用綁定右值int&&
時,匹配第二個重載,_Tp
爲int
,返回類型爲int&&
;
const int&&
同理。
綜上,std::forward
能完美轉發。
程序員老是要在Stack Overflow上撞撞牆才能學會一點東西。