在理想的轉發中, std::forward
用於將命名的右值引用t1
和t2
轉換爲未命名的右值引用。 這樣作的目的是什麼? 若是將t1
和t2
保留爲左值,這將如何影響被調用函數的inner
? ios
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
我認爲有一個實現std :: forward的概念代碼能夠增長討論的範圍。 這是斯科特·邁耶斯(Scott Meyers)講的《有效的C ++ 11/14採樣器》中的一張幻燈片 函數
代碼中的函數move
爲std::move
。 在該演講的前面有一個(有效的)實現。 我在libstdc ++中的move.h文件中找到了std :: forward的實際實現 ,但這根本沒有啓發性。 oop
從用戶的角度來看,其含義是std::forward
是有條件強制轉換爲右值。 若是我編寫的函數指望參數中包含左值或右值,而且僅當將其做爲右值傳遞時,但願將其做爲右值傳遞給另外一個函數,則該功能頗有用。 若是我沒有將參數包裝在std :: forward中,它將始終做爲常規引用傳遞。 spa
#include <iostream> #include <string> #include <utility> void overloaded_function(std::string& param) { std::cout << "std::string& version" << std::endl; } void overloaded_function(std::string&& param) { std::cout << "std::string&& version" << std::endl; } template<typename T> void pass_through(T&& param) { overloaded_function(std::forward<T>(param)); } int main() { std::string pes; pass_through(pes); pass_through(std::move(pes)); }
果真,它會打印 指針
std::string& version std::string&& version
該代碼基於前面提到的演講中的示例。 從大約15:00開始滑動10。 code
還沒有明確的一點是static_cast<T&&>
也能夠正確處理const T&
。
程序: htm
#include <iostream> using namespace std; void g(const int&) { cout << "const int&\n"; } void g(int&) { cout << "int&\n"; } void g(int&&) { cout << "int&&\n"; } template <typename T> void f(T&& a) { g(static_cast<T&&>(a)); } int main() { cout << "f(1)\n"; f(1); int a = 2; cout << "f(a)\n"; f(a); const int b = 3; cout << "f(const b)\n"; f(b); cout << "f(a * b)\n"; f(a * b); }
產生: 對象
f(1) int&& f(a) int& f(const b) const int& f(a * b) int&&
注意,「 f」必須是模板函數。 若是僅將其定義爲「 void f(int && a)」,則此方法無效。 get
若是將t1和t2保留爲左值,這將如何影響被調用函數的內部? 編譯器
若是在實例化以後, T1
是char
類型,而T2
是類,則您但願傳遞每一個副本t1
和每一個const
引用t2
。 好吧,除非inner()
每一個非const
引用都接受它們,也就是說,在這種狀況下,您也要這樣作。
嘗試編寫一組outer()
函數,該函數在沒有右值引用的狀況下實現此功能,從而推斷出從inner()
類型傳遞參數的正確方法。 我認爲您將須要2 ^ 2的東西,須要大量的模板元數據來推論參數,而且須要大量時間來在全部狀況下都作到這一點。
而後有人帶來了inner()
,它每一個指針都接受參數。 我認爲如今等於3 ^ 2。 (或4 ^ 2。該死,我沒必要費心去考慮const
指針是否會有所做爲。)
而後想象您要爲五個參數執行此操做。 或七個。
如今您知道了爲何有一些聰明的想法想出「完美轉發」的方法:它使編譯器爲您完成全部這些工做。
您必須瞭解轉發問題。 您能夠詳細閱讀整個問題 ,但我將進行總結。
基本上,給定表達式E(a, b, ... , c)
,咱們但願表達式f(a, b, ... , c)
等價。 在C ++ 03中,這是不可能的。 嘗試了不少,但都在某些方面失敗了。
最簡單的方法是使用左值引用:
template <typename A, typename B, typename C> void f(A& a, B& b, C& c) { E(a, b, c); }
但這沒法處理臨時值: f(1, 2, 3);
,由於這些不能綁定到左值引用。
下一個嘗試多是:
template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c) { E(a, b, c); }
能夠解決上述問題,但會出現觸發器。 如今,它不容許E
具備很是量參數:
int i = 1, j = 2, k = 3; void E(int&, int&, int&); f(i, j, k); // oops! E cannot modify these
第三次嘗試接受const-references,可是const_cast
將const
移開:
template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c) { E(const_cast<A&>(a), const_cast<B&>(b), const_cast<C&>(c)); }
這能夠接受全部值,能夠傳遞全部值,但可能致使未定義的行爲:
const int i = 1, j = 2, k = 3; E(int&, int&, int&); f(i, j, k); // ouch! E can modify a const object!
最終的解決方案能夠正確處理全部問題,但要以沒法維護爲代價。 使用const和non-const的全部組合提供f
重載:
template <typename A, typename B, typename C> void f(A& a, B& b, C& c); template <typename A, typename B, typename C> void f(const A& a, B& b, C& c); template <typename A, typename B, typename C> void f(A& a, const B& b, C& c); template <typename A, typename B, typename C> void f(A& a, B& b, const C& c); template <typename A, typename B, typename C> void f(const A& a, const B& b, C& c); template <typename A, typename B, typename C> void f(const A& a, B& b, const C& c); template <typename A, typename B, typename C> void f(A& a, const B& b, const C& c); template <typename A, typename B, typename C> void f(const A& a, const B& b, const C& c);
N個參數須要2 N個組合,這是一場噩夢。 咱們但願自動執行此操做。
(這其實是使編譯器在C ++ 11中爲咱們完成的工做。)
在C ++ 11中,咱們有機會解決此問題。 一種解決方案修改了現有類型的模板推導規則,但這可能會破壞大量代碼。 所以,咱們必須尋找另外一種方法。
解決方案是改成使用新添加的rvalue-references ; 咱們能夠在推導右值引用類型並建立任何所需結果時引入新規則。 畢竟,咱們如今不可能破壞代碼。
若是給出對引用的引用(請注意引用是一個包含T&
和T&&
的通用術語),咱們將使用如下規則來計算結果類型:
「給定類型T,它引用了類型T,嘗試建立類型「對cv TR的左值引用」會建立類型「對T的左值引用」,而嘗試建立對類型T的「右值引用」 cv TR」建立TR類型。」
或以表格形式:
TR R T& & -> T& // lvalue reference to cv TR -> lvalue reference to T T& && -> T& // rvalue reference to cv TR -> TR (lvalue reference to T) T&& & -> T& // lvalue reference to cv TR -> lvalue reference to T T&& && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)
接下來,使用模板參數推導:若是參數是左值A,則爲模板參數提供對A的左值引用。不然,咱們一般進行推論。 這給出了所謂的通用參考 (術語「 轉發參考」如今是正式參考 )。
爲何這有用? 由於合併在一塊兒,因此咱們能夠跟蹤類型的值類別:若是是左值,則有一個左值引用參數,不然有一個右值引用參數。
在代碼中:
template <typename T> void deduce(T&& x); int i; deduce(i); // deduce<int&>(int& &&) -> deduce<int&>(int&) deduce(1); // deduce<int>(int&&)
最後一件事是「轉發」變量的值類別。 請記住,一旦在函數內部,該參數就能夠做爲左值傳遞給任何對象:
void foo(int&); template <typename T> void deduce(T&& x) { foo(x); // fine, foo can refer to x } deduce(1); // okay, foo operates on x which has a value of 1
很差 E須要得到與咱們獲得的相同的價值類別! 解決方法是這樣的:
static_cast<T&&>(x);
這是作什麼的? 考慮咱們在deduce
函數內部,而且已經傳遞了一個左值。 這意味着T
是A&
,所以靜態類型轉換的目標類型是A& &&
,或者僅僅是A&
。 因爲x
已是A&
,因此咱們什麼也不作,只剩下一個左值引用。
當咱們傳遞了一個右值時, T
爲A
,所以靜態類型轉換的目標類型爲A&&
。 強制轉換會產生一個右值表達式, 該表達式再也不能夠傳遞給左值引用 。 咱們維護了參數的值類別。
將它們放在一塊兒可使咱們「完美轉發」:
template <typename A> void f(A&& a) { E(static_cast<A&&>(a)); }
當f
收到一個左值時, E
獲得一個左值。 當f
收到一個右值時, E
獲得一個右值。 完善。
固然,咱們要擺脫醜陋的境地。 static_cast<T&&>
晦澀難懂,難以記住; 讓咱們改成建立一個名爲forward
的實用程序函數,該函數執行相同的操做:
std::forward<A>(a); // is the same as static_cast<A&&>(a);
在理想的轉發中,std :: forward用於將命名的右值引用t1和t2轉換爲未命名的右值引用。 這樣作的目的是什麼? 若是將t1和t2保留爲左值,對被調用函數內部的影響如何?
template <typename T1, typename T2> void outer(T1&& t1, T2&& t2) { inner(std::forward<T1>(t1), std::forward<T2>(t2)); }
若是在表達式中使用命名的右值引用,則它其實是左值(由於您按名稱引用對象)。 考慮如下示例:
void inner(int &, int &); // #1 void inner(int &&, int &&); // #2
如今,若是咱們這樣稱呼outer
outer(17,29);
咱們但願將17和29轉發給#2,由於17和29是整數文字,而且是這樣的右值。 可是因爲表達式inner(t1,t2);
中的t1
和t2
inner(t1,t2);
是左值,則您將調用#1而不是#2。 這就是爲何咱們須要使用std::forward
將引用轉換回未命名的引用。 所以, outer
t1
始終是左值表達式,而forward<T1>(t1)
多是取決於T1
的右值表達式。 若是T1
是左值引用,則後者只是左值表達式。 而且,若是對external的第一個參數是左值表達式,則僅推導T1
爲左值引用。