[Effective Modern C++]導讀系列 — Item 2: Understand auto type deduction
Tags: Type, Deduction, Cpp, Efficiency, Programming Languages

Item 1介紹完 template 類型推導，接下來繼續介紹C++常見的 auto type 的類型推導，與 template 本質大致相同， auto 類型推導也分三種情况：

case 1：類型指定名稱是指針或引用，但不是 universal reference

const auto& rx = x; // case 1 (rx is a non-universal ref)

case 2：類型指定名稱是 universal reference

auto&& uref1 = x; // x is int and lvalue,

// so uref1’s type is int&. auto&& uref2 = cx; // cx is const int and lvalue,

// so uref2’s type is const int&. auto&& uref3 = 7; // rx is int and rvalue,

// so uref3’s type is int&&.

case 3：類型指定名稱既不是指針也不是引用

auto x = 7; // case 3 (x is neither ptr nor ref)

const auto cx = x; // case 3 (cx isn’t either)

且與 template 相同， auto 類型推導時，陣列和函數也會衰退為指針：

const char name[] = “Luka”; // name’s type is const char[5]

auto arr1 = name; // arr1’s type is const char*

auto& arr2 = name; // arr2’s type is const char (&)[5] void someFunc(int, double); // someFunc is a function; type is void(int, double)

auto func1 = someFunc; // func1’s type is void (*)(int, double)

auto& func2 = someFunc; // func2’s type is void (&)(int, double)

下面討論例外的情況，如果宣告一個 int 類型的變數並初始化為7，在 C++98 下有2種寫法：

int x1 = 7;

int x2(7);

C++11 支援了统一初始化(使用 {} 和 std::initializer_list ):

int x3 = { 7 };

int x4{ 7 };

之後Item 5 會解釋用 auto 代替固定類型宣告變數的好處:

auto x1 = 7; // type is int, value is 7

auto x2(7); // ditto

auto x3 = { 7 }; // type is std::initializer_list<int> value is {7}

auto x4{ 7 }; // ditto

這就是 auto 特殊的一個推導原則，如果用 auto 宣告變數時用时候用 {} ，類型會被推導成 std::initializer_list 。但 {} 的值類型並不相同，無法成功推導，compiler將報錯：

auto x5 = { 1, 2, 3.0 }; // error! can’t deduce T for std::initializer<T>

原因是因為這裡存在了兩種推導:

auto : x5 會被推導成 std::initializer_list std::initializer_list 是 template ，推導為 std::initializer_list<T>

因為型別不同，所以在推導 T 時會出錯。

auto x = { 7, 1, 4 }; // x’s type is std::initializer_list<int> template<typename T> // template with parameter

void f(T param); // declaration equivalent to x’s declaration

f({ 7, 1, 4}); // error! can’t deduce type for T template<typename T>

void f(std::initializer_list<T> param); f({ 7, 1, 4 }); // T deduced as int, and initList’s

// type is std::initializer_list<int>

所以 auto 和 template 類型推導的真正差異是 auto 可以推導统一初始化為 std::initializer_list ，而 template 不行

C++14 支援了 auto 為函數返回值以及 lambda 的參數宣告的推導。但是這裡的推導原則是採用 template 而不是 auto 的規則。所以在這些地方使用統一初始化會推導失敗:

// auto deduction for return value

auto createInitList()

{

return { 7, 1, 4 }; // error！ can’t deduce type for { 7, 1, 4 }

} // auto deductio for lambda parameter

std::vector<int> v; // … auto resetV =

[&v](const auto& newValue) { v = newValue; }; // … resetV({ 7, 1, 4 }); // error！ can’t deduce type for { 7, 1, 4 }

Summary

一般情况下 auto 類型推導與 template 相同，但是 auto 支援使用 {} 和 std::initializer_list 的推導，而 template 不行

類型推導與 相同，但是 支援使用 和 的推導，而 不行 auto 用在函數返回值& lambda 的參數宣告時，採用 template 的推導規則

New rules for direct-list-initialization

在 C++11 / C++ 14 中：

int x{ 7 }; // x’s type is int

auto y{ 7 }; // y’s type is std::initializer_list<int>

而在C++ 17 提出了 auto y{ 7 }; 應該推導成 int ：

#include <initializer_list> int main(int argc, char* argv[])

{

// brace-initialization, C++ 11 is std::initializer_list<int>,

// C++ 17 is int

auto x_single_brace_init{ 7 }; // copy-initialization, C++ 11 / C++ 17 is std::initializer_list<int>

auto x_single_copy_int = { 7 }; // brace-initialization, C++ 11 is std::initializer_list<int>

// C++ 17 is ill-formed

auto x_multi_brace_init{ 7, 1, 4 }; // copy-initialization, C++ 11 / C++ 17 is std::initializer_list<int>

auto x_multi_copy_init = { 7, 1, 4 }; return 0;

}

綜合以上，在 C++ 17 中的統一初始化的推導，在=之後才會將{}推導為 std::initializer_list 。其他情况 auto 類型推導與 template 完全相同