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 判定 x5std::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
C++11 經典錯誤

信奉 uniform initialization、習慣一律用大括號初始化的人,很容易不小心宣告出 std::initializer_list 變數而非預期型別。這也是部分開發者只在必要時才使用大括號的原因(何時必要見 Item 7)。

書外補充:C++17 起 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 }
Things to Remember(Item 2 核心)

  • 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 的處理方式