1. 程式人生 > 程式設計 >C++11 模板引數的“右值引用”是轉發引用嗎

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

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

void f(int&& i);

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

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;
  • T必須是該函式的模板引數。

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

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&&:

std::vector<int> vec;
for (auto&& i : vec)
{
  // ...
}

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

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

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



呼叫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上撞撞牆才能學會一點東西。

到此這篇關於C++11 模板引數的“右值引用”是轉發引用嗎的文章就介紹到這了,更多相關C++11 右值引用內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!