Item 2:理解 auto 型別推導 (Understand auto Type Deduction)
Overview Table
| 主題 | 重點 |
|---|---|
| auto 推導的本質 | 與 template type deduction 幾乎完全相同:auto 扮演 T,type specifier 扮演 ParamType |
| 三種 Case | 與 Item 1 相同:非 universal 的 reference/pointer、universal reference、pass-by-value |
| 唯一例外 | braced initializer({ ... })→ auto 推導為 std::initializer_list<T>;template 推導則直接失敗 |
| decay 規則 | 陣列名 / 函式名在非 reference specifier 下同樣 decay 成指標 |
| 函式回傳型別與 lambda 參數的 auto | 用的是 template type deduction,不是 auto type deduction → 不接受 { ... } |
auto 推導 = Template 推導的直接映射
宣告 auto 變數時,編譯器在概念上為每個宣告生成一個函式模板,再以初始化運算式呼叫它:auto 對應 T,整個 type specifier 對應 ParamType。
auto 變數宣告 概念上的模板呼叫
───────────────── ────────────────────────────
const auto& rx = x; template<typename T>
│ │ │ void func_for_rx(const T& param);
│ │ └─ expr ───► func_for_rx(x);
│ └────── auto = T
└─── type specifier = ParamType (const T&)
auto x = 27; // type specifier 是 auto 本身
const auto cx = x; // type specifier 是 const auto
const auto& rx = x; // type specifier 是 const auto&
// 編譯器視同以下模板推導:
template<typename T>
void func_for_x(T param); // 推導 x:param 的型別即 x 的型別 → int
template<typename T>
void func_for_cx(const T param); // 推導 cx:→ const int
template<typename T>
void func_for_rx(const T& param); // 推導 rx:→ const int&
三種 Case(與 Item 1 一一對應)
| Case | Type specifier 形式 | 範例 | 推導結果 |
|---|---|---|---|
| 1 | reference / pointer(非 universal) | const auto& rx = x; |
const int& |
| 2 | universal reference(auto&&) |
見下方 | lvalue → lvalue ref;rvalue → rvalue ref |
| 3 | 既非 pointer 也非 reference(by value) | auto x = 27; |
忽略 ref 與頂層 const/volatile → int |
int x = 27;
const int cx = x;
auto&& uref1 = x; // x 是 int 且為 lvalue → uref1 為 int&
auto&& uref2 = cx; // cx 是 const int lvalue → uref2 為 const int&
auto&& uref3 = 27; // 27 是 rvalue → uref3 為 int&&
陣列與函式名稱的 decay-to-pointer 規則也同樣適用:
const char name[] = "R. N. Briggs"; // name 型別:const char[13]
auto arr1 = name; // by value → decay 為 const char*
auto& arr2 = name; // reference → 保留陣列型別 const char (&)[13]
void someFunc(int, double); // 函式型別:void(int, double)
auto func1 = someFunc; // decay 為函式指標 void (*)(int, double)
auto& func2 = someFunc; // 函式參考 void (&)(int, double)
唯一例外:Braced Initializer 推導為 std::initializer_list
C++11 uniform initialization 帶來四種宣告語法,但換成 auto 後意義不再等價:
auto x1 = 27; // int,值為 27
auto x2(27); // 同上
auto x3 = { 27 }; // std::initializer_list<int>,含一個元素 27!
auto x4{ 27 }; // 同上(依原書 C++11/14 規則)
auto x5 = { 1, 2, 3.0 }; // 錯誤!initializer_list<T> 的 T 推導失敗
// (元素型別 int 與 double 不一致)
x5 其實牽涉兩層推導:先由 auto 判定 x5 是 std::initializer_list(因初始器帶大括號),再以 template 推導決定其 T——後者因元素型別不一致而失敗。
auto 與 template 面對 { ... } 的行為對照:
| 情境 | 結果 |
|---|---|
auto x = { 11, 23, 9 }; |
std::initializer_list<int> |
template<typename T> void f(T param); 呼叫 f({ 11, 23, 9 }); |
編譯錯誤:無法為 T 推導型別 |
template<typename T> void finitializer_list<T> il); 呼叫 f({ 11, 23, 9 }; |
OK:T 推導為 int |
信奉 uniform initialization、習慣一律用大括號初始化的人,很容易不小心宣告出 std::initializer_list 變數而非預期型別。這也是部分開發者只在必要時才使用大括號的原因(何時必要見 Item 7)。
auto x{ 27 } 的規則改變
依 N3922(C++17 正式採納,多數編譯器連 C++14 模式也套用):direct-list-initialization auto x4{ 27 }; 推導為 int(且大括號內僅允許單一元素);只有 copy-list-initialization auto x3 = { 27 }; 仍推導為 std::initializer_list<int>。原書描述的是採納前的 C++11/14 規則,面試時應說明版本差異。
auto 回傳型別與 Lambda 參數:走 Template 推導
C++14 允許函式回傳型別寫 auto(見 Item 3),也允許 lambda 參數用 auto(generic lambda)。但這兩處的 auto 採用 template type deduction 的規則,因此不接受 braced initializer:
auto createInitList()
{
return { 1, 2, 3 }; // 錯誤!無法為 { 1, 2, 3 } 推導型別
}
std::vector<int> v;
auto resetV = [&v](const auto& newValue) { v = newValue; }; // C++14
resetV({ 1, 2, 3 }); // 錯誤!同樣無法推導 { 1, 2, 3 }
- auto 型別推導通常與 template 型別推導相同,唯一差異:auto 假設 braced initializer 代表
std::initializer_list,template 推導則否(直接失敗)。 - 函式回傳型別或 lambda 參數中的
auto,實際上使用的是 template type deduction,而非 auto type deduction。
Exam/Test Patterns
| 情境關鍵字 | 答案 |
|---|---|
auto x = { 27 }; 的型別? |
std::initializer_list<int>(copy-list-init 特例) |
auto x = 27; / auto x(27); 的型別? |
int(一般 Case 3 推導) |
auto x = { 1, 2, 3.0 }; |
編譯錯誤:initializer_list<T> 的 T 推導不出單一型別 |
f({ 11, 23, 9 }) 傳入 template<typename T> void f(T); |
編譯錯誤:template 推導不假設 {} 是 initializer_list |
auto&& uref = 左值; |
推導為 lvalue reference(universal reference 特殊規則) |
auto arr = name;(name 為陣列) |
decay 為指標;auto& arr = name; 才保留陣列型別 |
回傳 { 1, 2, 3 } 的 auto 回傳型別函式 |
編譯錯誤:回傳型別的 auto 用 template 推導 |
generic lambda 的 auto 參數收 { ... } |
編譯錯誤:同上,屬 template 推導 |
| auto 與 template 推導的「唯一差異」 | braced initializer 的處理方式 |
Related Notes
- 02-Deducing-Types/01-Template-Type-Deduction — auto 推導的基礎:三種 Case、decay 規則全部沿用 Item 1
- 02-Deducing-Types/03-Decltype — 第三套推導規則;
decltype(auto)以 decltype 規則取代 auto 規則 - 02-Deducing-Types/04-Viewing-Deduced-Types — 不確定 auto 推導結果時,檢視型別的工具
- 03-Auto/01-Prefer-Auto-To-Explicit-Types — 為何優先使用 auto 宣告變數
- 03-Auto/02-Explicitly-Typed-Initializer-Idiom — auto 推導出非預期 proxy type 時的對策
- 04-Moving-To-Modern-Cpp/01-Braced-Initialization —
()vs{}:braced initializer 的完整規則 - 06-Move-Semantics-And-Perfect-Forwarding/08-Perfect-Forwarding-Failure-Cases — braced initializer 也是 perfect forwarding 的失敗案例