Auto (C++)

auto是C++程序設計語言的關鍵字。自C++11以來，auto關鍵字用於兩種情況：聲明變量時根據初始化表達式自動推斷該變量的類型、聲明函數時函數返回值的占位符。C++98標準中auto關鍵字用於自動變量的聲明，但由於使用極少且多餘，在C++11中已刪除這一用法。

## 簡要理解

auto可以在聲明變量時根據變量初始值的類型自動為此變量選擇匹配的類型。C++語言類似的關鍵字還有decltype。

舉例：對於值x=1；即可以聲明：int x = 1或long x = 1，也可以直接聲明auto x = 1。

其它語言的類似功能包括C#的var關鍵字。

## 用法

根據初始化表達式自動推斷被聲明的變量的類型，如：

```
 auto f=3.14;      //double
 auto s("hello");  //const char*
 auto z = new auto(9); // int*
 auto x1 = 5, x2 = 5.0, x3='r';//錯誤，必須是同一類型
```

但是，這麼簡單的變量聲明類型，不建議用auto關鍵字，而是應更清晰地直接寫出其類型。

auto關鍵字更適用於類型冗長複雜、變量使用範圍專一時，使程序更清晰易讀：

```
 std::vector<int> vect; 
 for(auto it = vect.begin(); it != vect.end(); ++it)
 {  //it的類型是std::vector<int>::iterator
    std::cin >> *it;
  }
```

或者保存lambda表達式類型的變量聲明：

```
  auto ptr = [](double x){return x*x;};//类型为std::function<double(double)>函数对象
```

在模板函數定義時，如果變量的類型依賴於模板參數，使用auto關鍵字使得在編譯期確定這些類型，如：

```
 template <class T, class U>void Multiply(T t, U u)
 {
      auto v = t * u;
      std::cout<<v;
 }
```

模板函數的返回類型如果也是依賴於從模板參數推導。

```
 template <typename _Tx, typename _Ty>
 auto multiply(_Tx v1, _Ty v2) -> decltype( _Tx * _Ty )
 {
     return v1*v2;
 }
 auto result = multiply(101, 1.414); // 结果类型是double
```

## 語義

使用auto關鍵字做類型自動推導時，依次施加以下規則：

- 首先，如果初始化表達式是引用，首先去除引用。
- 上一步後，如果剩下的初始化表達式有頂層的const且/或volatile限定符，去除掉。

這一組規則同於模板函數的模板參數推導（template argument deduction）時的規則。但auto關鍵字可以從C++11風格的花括號{與}包圍的值列表推導出std::initializer_list；而模板函數的形參推導時不認為這種值列表是一個類型，因此不能由值列表推導出std::initializer_list類型。

因而，使用auto關鍵字聲明變量的類型，不能自動推導出頂層的CV-qualifiers，也不能自動推導出引用類型，需要顯式指定。例如：

```
   const int v1 = 101;
   auto v2 = v1;       // v2类型是int，脱去初始化表达式的顶层const
   v2=102；            // 可赋值
   auto al = { 10, 11, 12 };//类型是std::initializer_list<int>
   template<class T> void foo(T arg); // 函数模板声明
   foo(v2); //函数模板实例化为 void foo<int>(int)
```

如果需要具有頂層的CV-qualifiers，或者引用的類型，解決辦法是顯式指明：

```
   const auto& v3=v1;
   foo<const int&>(v1);//直接指明模板参数类型
   template<class T> void foo(const T& arg);//或者偏特化模板函数
```

如果auto關鍵字還帶上&號，聲明引用類型，則不執行const剝除（const-stripping），例如：

```
   const int c = 0;
   auto& rc = c;
   rc = 44; // 编译错误，const int类型
```

這是因為如果不抑制const剝除，則得到了一個非常量引用型變量，指向了const變量，這顯然是不可接受的。模板參數推導也遵循此規則。

初始化表達式為陣列，auto關鍵字推導的類型為指針。這是因為陣列名在初始化表達式中自動隱式轉換為首元素地址的右值。例如：

```
   int a[9];
   auto j = a;
   std::cout << typeid(j).name() << " "<<sizeof(j)<<" "<<sizeof(a)<< std::endl;
```

由於C++規定字串字面量是左值，因此可以通過&運算符直接取地址：

```
   auto al = &"hello";  // a1的类型是const char(*) [6]  
```

### auto關鍵字的類型完美轉發

C++11使用auto聲明變量時，如：`auto&& var=initValue;`「auto&&」並不意味著這一定是右值引用類型的變量，而是類似於模板函數參數的類型推導，既可能是左值引用，也可能是右值引用。其目的是把初始化表達式的值分類情況，完美轉發給由auto聲明的變量。也即：

- 如果初始化值（initializer）是類型A的左值，則聲明的變量類型為左值引用A&。
- 如果初始化值是類型A的右值，則聲明的變量類型為右值引用A&&。

```cpp
 
#include<iostream>  
#include <vector>
#include <typeinfo>
using namespace std;
 
struct Widget{};
Widget makeWidget(){ return Widget(); } // 类工厂函数

int main()
{
	Widget&& var1 = Widget(); // var1的类型是右值引用，但是作为左值
	auto&& var2 = var1;       //var2的类型是左值引用
	std::vector<int> v = { 1, 2, 3 };
	auto&& val = v[0]; // std::vector::operator[]的返回值是元素左值，所以val的类型是左值引用
	Widget&& var3 = makeWidget(); // var3是左值，但它的类型是右值引用 
	Widget var4 = static_cast<Widget&&>(var1); // var4是左值，但它的类型是右值引用

	std::cout << typeid(var1).name() << std::endl;
	std::cout << typeid(var2).name() << std::endl;
	std::cout << typeid(val).name() << std::endl;
	std::cout << typeid(var3).name()  << std::endl;
	std::cout << typeid(var4).name() << std::endl;
}
```