使用前進的優點

在理想的轉發中, std::forward用於將命名的右值引用t1t2轉換爲未命名的右值引用。 這樣作的目的是什麼? 若是將t1t2保留爲左值,這將如何影響被調用函數的innerios

template <typename T1, typename T2>
void outer(T1&& t1, T2&& t2) 
{
    inner(std::forward<T1>(t1), std::forward<T2>(t2));
}

#1樓

我認爲有一個實現std :: forward的概念代碼能夠增長討論的範圍。 這是斯科特·邁耶斯(Scott Meyers)講的《有效的C ++ 11/14採樣器》中的一張幻燈片 函數

實現std :: forward的概念代碼

代碼中的函數movestd::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


#2樓

還沒有明確的一點是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


#3樓

若是將t1和t2保留爲左值,這將如何影響被調用函數的內部? 編譯器

若是在實例化以後, T1char類型,而T2是類,則您但願傳遞每一個副本t1和每一個const引用t2 。 好吧,除非inner()每一個非const引用都接受它們,也就是說,在這種狀況下,您也要這樣作。

嘗試編寫一組outer()函數,該函數在沒有右值引用的狀況下實現此功能,從而推斷出從inner()類型傳遞參數的正確方法。 我認爲您將須要2 ^ 2的東西,須要大量的模板元數據來推論參數,而且須要大量時間來在全部狀況下都作到這一點。

而後有人帶來了inner() ,它每一個指針都接受參數。 我認爲如今等於3 ^ 2。 (或4 ^ 2。該死,我沒必要費心去考慮const指針是否會有所做爲。)

而後想象您要爲五個參數執行此操做。 或七個。

如今您知道了爲何有一些聰明的想法想出「完美轉發」的方法:它使編譯器爲您完成全部這些工做。


#4樓

您必須瞭解轉發問題。 您能夠詳細閱讀整個問題 ,但我將進行總結。

基本上,給定表達式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_castconst移開:

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函數內部,而且已經傳遞了一個左值。 這意味着TA& ,所以靜態類型轉換的目標類型是A& && ,或者僅僅是A& 。 因爲x已是A& ,因此咱們什麼也不作,只剩下一個左值引用。

當咱們傳遞了一個右值時, TA ,所以靜態類型轉換的目標類型爲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);

#5樓

在理想的轉發中,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);中的t1t2 inner(t1,t2); 是左值,則您將調用#1而不是#2。 這就是爲何咱們須要使用std::forward將引用轉換回未命名的引用。 所以, outer t1始終是左值表達式,而forward<T1>(t1)多是取決於T1的右值表達式。 若是T1是左值引用,則後者只是左值表達式。 而且,若是對external的第一個參數是左值表達式,則僅推導T1爲左值引用。

相關文章
相關標籤/搜索