c++11-17 模板核心知識(十)—— 區分萬能引用(universal references)和右值引用


引子

T&&在代碼里並不總是右值引用:

void f(Widget&& param);      // rvalue reference

Widget&& var1 = Widget();      // rvalue reference

auto&& var2 = var1;        // not rvalue reference


template<typename T>
void f(std::vector<T>&& param);      // rvalue reference


template<typename T>
void f(T&& param);     // not rvalue reference

T&&代表兩種含義:

  • 右值引用
  • 萬能引用(universal references, or forwarding references)

如何區分

萬能引用一般出現在兩個場景中:

  • 模板參數
template<typename T>
void f(T&& param); // param is a universal reference
  • auto聲明
auto&& var2 = var1; // var2 is a universal reference

我們分別討論下這兩種場景。

模板參數

我們注意到,涉及到萬能引用的地方,都會有參數推導的過程,例如上面的T和var2. 而右值引用則沒有這個過程:

void f(Widget&& param);        // no type deduction; param is an rvalue reference

Widget&& var1 = Widget();     // no type deduction; var1 is an rvalue reference

但是即使語句設計到參數推導,也不一定就是萬能引用。例如:

template<typename T>
void f(std::vector<T>&& param);       // param is an rvalue reference


std::vector<int> v;
f(v); // error! can't bind lvalue to rvalue reference

這點還是比較好理解的。萬能引用需要依靠表達式來初始化自己是右值引用還是左值引用,但是上面這個例子沒有表現出這一點,它僅僅是推斷了T的類型,但是param的類型一直都是std::vector<T>&&

我們再舉一個vector中的例子:

template<class T, class Allocator = allocator<T>> 
class vector { 
public:

void push_back(T&& x);      // rvalue reference


template <class... Args> 
void emplace_back(Args&&... args);      // universal reference
};
  • push_back(T&& x)中的T&&為右值引用,因為這個雖然是T&&,但是不涉及到參數推導。當push_back被instantiated時,實際的調用類似於:
std::vector<Widget> v;

...
class vector<Widget, allocator<Widget>> {
public:
void push_back(Widget&& x);       // rvalue reference
…
};

可以很明顯的看出此時沒有參數推導的過程。

  • template <class... Args> emplace_back(Args&&... args)中的Args&&為萬能引用。Args與T是相互獨立的,所以Args有一個獨立的參數推斷過程。

const disqualify universal reference

有意思的是,當參數加上const后,就一定是右值引用:

template <class T> int f(T&& heisenreference);
template <class T> int g(const T&&);

int i;
int n1 = f(i);         // calls f<int&>(int&)
int n2 = f(0);     // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which would bind an rvalue reference to an lvalue

至於為什么會有這個規定,按照Why adding const makes the universal reference as rvalue的說法,大體有兩點原因:

  • const T&&允許你重載一個函數模板,它只接受右值引用。如果const T&&也被當做universal reference,那么將沒有辦法讓函數只接受右值引用。
  • 顯示禁用某個函數接受右值引用:template <typename T> void cref(const T&&) = delete;

auto聲明

對於auto的場景來說,所有的auto&&都是萬能引用,因為它總是有參數推導的過程。例如定義一個記錄函數執行時間的lambda(C++14中允許使用auto來聲明lambda的函數):

auto timeFuncInvocation = [](auto &&func, auto &&... params) {  
  start timer;
  std::forward<decltype(func)>(func)(                      // invoke func
      std::forward<decltype(params)>(params)...      // on params
  );
  stop timer and record elapsed time;
};

(完)

朋友們可以關注下我的公眾號,獲得最及時的更新:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM