《Effective Modern C++》学习笔记 - Item 2: 理解auto类型推导

  • 除了一种特殊情况外,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);
  • 类似于模板推导的三种情况:
    1. Case 1: 类型标识符(type specifier)是指针或引用,但不是万能引用。
    2. Case 2:类型标识符是万能引用。
    3. 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 使用 大括号初始化,首先要推导出 xstd::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

总结

  1. auto 类型推导通常与模板类型推导相同,但 auto 类型推导将大括号包裹的初始化器(initializer)识别为 std::initializer_list,而模板类型推导不会。
  2. auto 在函数返回值类型或lambda函数参数中遵循模板类型推导规则,而不是auto 类型推导的规则。
上一篇:grid布局总结


下一篇:python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)