Effective Modern C++ Item 2

Understand auto type deduction

除了一个例外,auto类型推导就是模版类型推导。

模板类型推导使用下面这个函数模板,

1
2
3
template<typename T>
void f(ParmaType param);
f(expr); //使用一些表达式调用f

而当一个变量使用auto进行声明时,auto扮演了模板中T的角色,变量的类型说明符扮演了ParamType的角色。例如:

1
2
3
auto x = 27;
const auto cx = x;
const auto& rx = x;

我们可以认为生成了一些模板并推断出 auto 关键字的类型, 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename T>            //概念化的模板用来推导x的类型
void func_for_x(T param);

func_for_x(27); //概念化调用:
//param的推导类型是x的类型

template<typename T> //概念化的模板用来推导cx的类型
void func_for_cx(const T param);

func_for_cx(x); //概念化调用:
//param的推导类型是cx的类型

template<typename T> //概念化的模板用来推导rx的类型
void func_for_rx(const T & param);

func_for_rx(x); //概念化调用:
//param的推导类型是rx的类型

Item1描述的三个情景稍作修改就能适用于auto:

  • 情景一:类型说明符是一个指针或引用但不是通用引用
  • 情景二:类型说明符一个通用引用
  • 情景三:类型说明符既不是指针也不是引用

上文中描述了情况一和情况三,现在描述情况二:

1
2
3
4
5
6
auto&& uref1 = x;               //x是int左值,
//所以uref1类型为int&
auto&& uref2 = cx; //cx是const int左值,
//所以uref2类型为const int&
auto&& uref3 = 27; //27是int右值,
//所以uref3类型为int&&

数组和函数名退化为指针同样适用于auto类型推导,这里就不举例了。

但是, 这里有一个例外需要我们注意:当auto声明变量的初始值设定项括在花括号”{}“中时, 推导类型为 std::initializer_list. 如果不能推导出这样的类型, 代码将被拒绝编译。

1
2
3
4
5
6
auto x1 = 27;                   //类型是int,值是27
auto x2(27); //同上
auto x3 = { 27 }; //类型是std::initializer_list<int>,
//值是{ 27 }
auto x4{ 27 }; //同上
auto x5 = { 1, 2, 3.0 }; //错误!无法推导std::initializer_list<T>中的T

这里发生了两种类型推导。一种是由于auto的使用:使用花括号进行初始化的x5必须被推导为std::initializer_list。但是std::initializer_list是一个模板。std::initializer_list<T>会被某种类型T实例化,所以这意味着T也会被推导。 在这个例子中推导之所以失败,是因为在花括号中的值并不是同一种类型。

因此auto类型推导和模板类型推导的真正区别在于,auto类型推导假定花括号表示std::initializer_list而模板类型不能推导花括号。

C++14允许auto用于函数返回值并会被推导,C++14的lambda函数也允许在形参声明中使用auto。但是在这些情况下auto使用模板类型推导的规则,而不是auto类型推导。因此, 具有返回花括号初始化器的auto返回类型的函数将无法编译:

1
2
3
4
5
6
7
8
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 }的类型

重点

  • auto类型推导通常与模板类型推导相同, 但auto类型推导假定花括号(“{}“)初始化表示 std::initializer_list,而模板类型推导则不然。
  • 函数返回类型或 lambda 参数中的 auto 表示模板类型推导,而不是auto类型推导。