- 除了一种特殊情况外,
auto
类型推导就是模板类型推导(尽管二者在形式上看起来不同) - 当变量用
auto
声明时,auto
取代了模板中T
的角色,而变量的类型等同于ParamType
。下面的例子展示了二者的等价性:
auto x = 27; // 等价于以下模板推导
template<typename T> // 推导x类型的假想模板,下同
void func_for_x(T param);
func_for_x(27);
const auto cx = x; // 等价于以下模板推导
template<typename T>
void func_for_cx(const T param);
func_for_cx(x);
const auto& rx = x; // 等价于以下模板推导
template<typename T>
void func_for_rx(const T& param);
func_for_rx(x);
- 类似于模板推导的三种情况:
- Case 1: 类型标识符(type specifier)是指针或引用,但不是万能引用。
- Case 2:类型标识符是万能引用。
- Case 3: 类型标识符既不是指针也不是引用
- 上面的例子已经展示了Case 1和3,Case 2的运作方式也如预期:
auto&& uref1 = x; // x是左值int,所以uref1是int&
auto&& uref2 = cx; // cx是左值const int,所以uref2是const int&
auto&& uref3 = 27; // 27是右值int,所以uref3是int&&
- 对数组和函数的讨论也对
auto
成立:
const char name[] = "hello";
auto arr1 = name; // arr1是const char*
auto& arr2 = name; // arr2是const char (&)[13]
void someFunc(int, double);
auto func1 = someFunc; // func1是void (*)(int, double)
auto& func2 = someFunc; // func2是void (&)(int, double)
- 二者只在一个方面不同。C++11引入了统一初始化(uniform initialization),以下四种初始化方式的结果是一样的:
// 都将x初始化为int值27
int x1 = 27;
int x2(27);
int x3 = { 27 };
int x4{ 27 };
但如果直接将标识符改为 auto
,这些声明仍都能通过编译,但意思不同。前两个仍声明一个值为27的 int
,但后两个声明的是包含一个元素27的 std::initializer_list<int>
!
auto x1 = 27; // 类型为int,值为27
auto x2(27); // 同上
auto x3 = { 27 }; // 类型为std::initializer_list<int>, 值为 { 27 }
auto x4{ 27 }; // 同上
这是由于 auto
类型推导的一条特殊规则:当使用大括号包围的初始化器(initializer)对一个 auto
描述的变量进行初始化时,推导出的类型是 std::initializer_list
。如果推导不出这样的类型(如大括号包围的初始化器中有多个不同类型的元素)时,代码会编译报错。
要理解这里其实是一个两步的推导过程:由于 x
使用 大括号初始化,首先要推导出 x
是 std::initializer_list<T>
,而后者又是一个模板,所以要从初始化列表中推出 T
的类型。
以上适用于 auto
的规则并不适用于模板推导,例如以下代码会报错:
template<typename T>
void f(T param);
f({ 11, 23, 9 }); // error!
解决方法为:
template<typename T>
void f(std::initializer_list<T> initList);
f({ 11, 23, 9 });
- C++14还允许使用
auto
推导函数返回值,以及在lambda函数中使用auto
声明参数。然而auto
的这些使用遵循的是模板类型推导的规则,而不是auto
类型推导的规则,所以以下代码均会报错:
auto createInitList()
{
return { 1, 2, 3 }; // error
}
std::vector<int> v;
auto resetV =
[&v](const auto& newValue) { v = newValue; }; // C++14
resetV({ 1, 2, 3 }); // error
总结
-
auto
类型推导通常与模板类型推导相同,但auto
类型推导将大括号包裹的初始化器(initializer)识别为std::initializer_list
,而模板类型推导不会。 -
auto
在函数返回值类型或lambda函数参数中遵循模板类型推导规则,而不是auto
类型推导的规则。