歡迎您光臨本站 註冊首頁

C++11 模板參數的“右值引用”是轉發引用嗎

←手機掃碼閱讀     qp18502452 @ 2020-06-10 , reply:0

在C++11中,&&不再只有邏輯與的含義,還可能是右值引用:

  void f(int&& i);

 

但也不盡然,&&還可能是轉發引用:

  templatevoid g(T&& obj);

 

“轉發引用”(forwarding reference)舊稱“通用引用”(universal reference),它的“通用”之處在於你可以拿一個左值綁定給轉發引用,但不能給右值引用:

  void f(int&& i) { }    templatevoid g(T&& obj) { }    int main()  {    int n = 2;    f(1);  // f(n); // error    g(1);    g(n);  }

 

一個函數的參數要想成為轉發引用,必須滿足:

  • 參數類型為T&&,沒有const或volatile;

  • T必須是該函數的模板參數。

換言之,以下函數的參數都不是轉發引用:

  templatevoid f(const T&&);  templatevoid g(typename std::remove_reference&&);  templateclass A  {    templatevoid h(T&&, const U&);  };

 

另一種情況是auto&&變量也可以成為轉發引用:

  auto&& vec = foo();

 

所以寫範圍for循環的最好方法是用auto&&:

  std::vectorvec;  for (auto&& i : vec)  {    // ...  }

 

有一個例外,當auto&&右邊是初始化列表,如auto&& l = {1, 2, 3};時,該變量為std::initializer_list&&類型。

轉發引用,是用來轉發的。只有當你的意圖是轉發參數時,才寫轉發引用T&&,否則最好把const T&和T&&寫成重載(如果需要的話還可以寫T&,還有不常用的const T&&;其中T是具體類型而非模板參數)。

轉發一個轉發引用需要用std::forward,定義在中:


 

調用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不需要而std::forward需要呢?這得從std::forward的簽名說起:

  templateconstexpr T&& forward(std::remove_reference_t&) noexcept;  templateconstexpr T&& forward(std::remove_reference_t&&) noexcept;

 

調用std::forward時,編譯器無法根據std::remove_reference_t反推出T,從而實例化函數模板,因此需要手動指明。

但是這並沒有從根本上回答問題,或者可以進一步引出新的問題――為什麼std::forward的參數不定義成T&&呢?

原因很簡單,T&&會把T&、const T&、T&&和const T&&(以及對應的volatile)都吃掉,有了T&&以後,再寫T&也沒用。

且慢,T&&參數在傳入函數是會匹配到T&&嗎?

  #include#includevoid 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++:

  templateconstexpr _Tp&&   forward(typename std::remove_reference::type& __t) noexcept   { return static_cast(__t); }    templateconstexpr _Tp&&   forward(typename std::remove_reference::type&& __t) noexcept   {    static_assert(!std::is_lvalue_reference::value, "template argument"           " substituting _Tp is an lvalue reference type");    return static_cast(__t);   }

 

  • 當轉發引用T&& obj綁定左值int&時,匹配第一個重載,_Tp即T為int&,返回類型_Tp&&為int&(引用摺疊:& &、& &&、&& &都摺疊為&,只有&& &&摺疊為&&);

  • const int&同理;

  • 當轉發引用綁定右值int&&時,匹配第二個重載,_Tp為int,返回類型為int&&;

  • const int&&同理。

綜上,std::forward能完美轉發。

程序員總是要在Stack Overflow上撞撞牆才能學會一點東西。



[qp18502452 ] C++11 模板參數的“右值引用”是轉發引用嗎已經有245次圍觀

http://coctec.com/docs/c/language/show-post-237857.html