Item 24:區分 universal reference 與 rvalue reference (Distinguish Universal References from Rvalue References)
Overview Table
| 重點 | 內容 |
|---|---|
| 核心問題 | 原始碼中的 T&& 不一定是 rvalue reference——它有兩種身分 |
T&& 的兩種身分 |
rvalue reference(只綁 rvalue)或 universal reference(rvalue 與 lvalue 都能綁) |
| Universal reference 成立條件 | (1) 發生型別推導(type deduction)+ (2) 宣告形式恰好是 T&&(type&&,不能多任何修飾) |
| 兩大出現場景 | 函式模板參數 template<typename T> void f(T&& param);auto&& 變數宣告 |
| 綁定能力 | 可綁 lvalue / rvalue / const / non-const / volatile——幾乎任何東西 |
| 最終身分由誰決定 | 初始器(initializer):rvalue 初始化 → 對應 rvalue reference;lvalue 初始化 → 對應 lvalue reference |
| 常見破格情形 | std::vector<T>&&、const T&&、模板類別成員函式的 T&&(如 push_back)→ 全是 rvalue reference |
| 背後真相 | universal reference 是一種抽象(abstraction),底層機制是 reference collapsing(Item 28) |
本書出版時,C++ 社群已開始將 universal reference 稱為 forwarding reference(C++17 標準採用此名)。兩者是同一概念。
T&& 的兩種意義與判斷流程
T&& 可能是 rvalue reference,也可能是 universal reference;關鍵在於是否發生型別推導且形式是否精確為 type&&——兩個條件缺一不可:
看到 "type&&" 宣告
│
▼
有發生型別推導嗎?(函式模板參數 / auto&&)
│
┌──────┴──────┐
否 是
│ ▼
│ 宣告形式「恰好」是 T&& 嗎?
│ (vector<T>&&、const T&& 都不算)
│ │
│ ┌────┴────┐
│ 否 是
▼ ▼ ▼
rvalue reference universal reference
(只綁 rvalue) │
初始器是 lvalue → 表現為 lvalue reference (T&)
初始器是 rvalue → 表現為 rvalue reference (T&&)
void f(Widget&& param); // 無型別推導 → rvalue reference
Widget&& var1 = Widget(); // 無型別推導 → rvalue reference
auto&& var2 = var1; // auto 推導 + 形式 auto&& → universal reference
// (var1 是 lvalue,故 var2 為 lvalue reference)
template<typename T>
void fvector<T>&& param; // 形式是 vector<T>&&,非 T&& → rvalue reference
template<typename T>
void f(const T&& param); // 多了 const → rvalue reference(喪失 universal 資格)
template<typename T>
void f(T&& param); // 型別推導 + 精確 T&& → universal reference
| 種類 | 可綁定對象 | 主要用途 |
|---|---|---|
rvalue reference(Widget&&,無推導) |
只有 rvalue | 識別「可被 move 的物件」 |
universal reference(T&& + 推導) |
rvalue、lvalue、const、非 const、volatile……幾乎任何東西 | 完美轉發(perfect forwarding) |
std::vector<int> v;
f(v); // 對 fvector<T>&& 而言:錯誤!lvalue 不能綁到 rvalue reference
初始器決定 universal reference 的最終身分
universal reference 是 reference,必須被初始化;初始器是 rvalue 還是 lvalue 決定它「變成」哪種 reference。函式參數的初始器由呼叫端(call site)提供:
template<typename T>
void f(T&& param); // param 是 universal reference
Widget w;
f(w); // 傳入 lvalue → T 推導為 Widget&,
// param 型別為 Widget&(lvalue reference)
fmove(w); // 傳入 rvalue → T 推導為 Widget,
// param 型別為 Widget&&(rvalue reference)
| 初始器 | universal reference 的表現 | T 的推導結果 |
|---|---|---|
lvalue(如 w) |
對應 lvalue reference(Widget&) |
T = Widget& |
rvalue(如 std::move(w)、暫時物件) |
對應 rvalue reference(Widget&&) |
T = Widget |
「在模板裡」不等於「有型別推導」:push_back vs emplace_back
身處模板中的 T&& 參數不保證是 universal reference——經典對照是 std::vector 的兩個成員函式:
template<class T, class Allocator = allocator<T>>
class vector {
public:
void push_back(T&& x); // 形式對,但 T 在 vector 實體化時已固定,
// 呼叫 push_back 時無型別推導 → rvalue reference
template <class... Args>
void emplace_back(Args&&... args); // Args 獨立於 T,每次呼叫都要推導
// → universal reference(參數包)
};
std::vector<Widget> v; // 實體化後:void push_back(Widget&& x);
// 清楚可見是 rvalue-reference-to-Widget
| 成員函式 | 呼叫時有型別推導? | 參數性質 |
|---|---|---|
push_back(T&& x) |
否(T 隨 vector<Widget> 實體化早已固定) |
rvalue reference |
emplace_back(Args&&... args) |
是(Args 每次呼叫獨立推導) |
universal reference |
T
只要「形式為 type&& 且該 type 被推導」就是 universal reference——MyTemplateType&&、Args&& 都算。重點在形式,不在名稱。
若呼叫端顯式指定模板引數(如 f<Widget>(w)),型別推導不會發生,此為本 Item 刻意忽略的 edge case。此外,「universal reference」整個概念本身就是一個簡化的抽象——底層真相是 reference collapsing,詳見 Item 28。
auto&& universal reference 與 C++14 lambda
以 auto&& 宣告的變數同樣滿足「推導 + 正確形式」,因此是 universal reference。C++14 起 lambda 可宣告 auto&& 參數,使 universal reference 大量出現:
// C++14:計時任意函式呼叫的 lambda
auto timeFuncInvocation =
[](auto&& func, auto&&... params) // func、params 都是
{ // universal reference
// 啟動計時器
std::forward<decltype(func)>(func)( // 呼叫 func
std::forward<decltype(params)>(params)... // 完美轉發所有參數
);
// 停止計時器並記錄耗時
};
// func:可綁定任何 callable(lvalue 或 rvalue)
// params:universal reference 參數包,可綁定任意數量、任意型別的引數
timeFuncInvocation 能計時「幾乎」任何函式呼叫,並非全部——perfect forwarding 存在失敗案例(braced initializer、0/NULL 作為 null 指標等),詳見 Item 30。std::forward<decltype(...)> 慣用法的原理見 Item 33。
universal reference 底層的真相是 reference collapsing(Item 28)。但如同牛頓力學之於相對論,這個抽象通常更好用:它讓你準確閱讀程式碼(「這個 T&& 只綁 rvalue,還是什麼都綁?」)、與同事精確溝通,並且是理解 Item 25、26 的前提。
Exam/Test Patterns
| 情境關鍵字 | 答案 |
|---|---|
void f(Widget&& param) 的 param 是什麼 |
rvalue reference(無型別推導) |
template<typename T> void f(T&& param) 的 param 是什麼 |
universal reference(推導 + 精確 T&& 形式) |
auto&& var2 = var1; 的 var2 是什麼 |
universal reference;var1 是 lvalue → var2 為 lvalue reference |
template<typename T> void fvector<T>&& param |
rvalue reference(形式非精確 T&&);傳 lvalue 會編譯錯誤 |
template<typename T> void f(const T&& param) |
rvalue reference(const 取消 universal 資格) |
| universal reference 以 lvalue 初始化 | 表現為 lvalue reference(T 推導為 Widget&) |
| universal reference 以 rvalue 初始化 | 表現為 rvalue reference(T 推導為 Widget) |
vector<T>::push_back(T&& x) 是 universal reference 嗎 |
不是——實體化時 T 已固定,呼叫時無推導 |
emplace_back(Args&&... args) 是 universal reference 嗎 |
是——Args 每次呼叫獨立推導,且形式正確 |
| universal reference 的別名 | forwarding reference(轉發參考) |
| universal reference 的底層機制 | reference collapsing(Item 28);universal reference 是實用的抽象 |
| universal reference 該搭配哪個轉型工具 | std::forward(rvalue reference 才用 std::move,見 Item 25) |
Related Notes
- 06-Move-Semantics-And-Perfect-Forwarding/01-Std-Move-And-Std-Forward — Item 23:
std::move無條件轉 rvalue、std::forward條件式轉型,是理解兩種 reference 用途的基礎 - 06-Move-Semantics-And-Perfect-Forwarding/03-Move-Vs-Forward-Usage — Item 25:rvalue reference 用
std::move、universal reference 用std::forward,依賴本 Item 的區分 - 06-Move-Semantics-And-Perfect-Forwarding/04-Overloading-On-Universal-References — Item 26:universal reference 幾乎能綁任何東西,因此對其重載非常危險
- 06-Move-Semantics-And-Perfect-Forwarding/06-Reference-Collapsing — Item 28:universal reference 抽象背後的真相
- 06-Move-Semantics-And-Perfect-Forwarding/08-Perfect-Forwarding-Failure-Cases — Item 30:
auto&&lambda 只能轉發「幾乎」所有引數的原因 - 02-Deducing-Types/01-Template-Type-Deduction — Item 1:universal reference 的推導規則(lvalue 推導出
T&)正是模板型別推導的特例 - 07-Lambda-Expressions/03-Decltype-Auto-Forwarding — Item 33:lambda 的
auto&&參數如何搭配std::forward<decltype(x)>