[译] Item 2: Understand Auto Type Deduction
如果你已经阅读了 Item 1 模板类型推导,那么你应该已经掌握了 auto
的类型推导,出了下面要讲到的一个不同之外,其他都和 Item 1 完全一致,但是你肯定还有疑问,为什么模板类型推导涉及到了模板,函数以及参数,而 auto
却不涉及这些。
没错,但是这也没什么关系。其实模板类型推导与 auto
类型推导有直接的映射关系。有很直观的转换关系。
在 Item 1 中,我们使用下面的模板函数用来描述模板类型推导,
1 2 |
|
调用如下:
1
|
|
在调用函数 f
时,编译器使用表达式 expr
来推导类型 T
和 ParamType
。
当使用 auto
来声明变量时,auto
代替了上面 T
的位置,同时变量的类型就是 ParamType
的类型。看下面的例子会更直观,
1
|
|
在这里,变量 x
的类型标识符就是一个简单的 auto
,在
1
|
|
中,类型标识符是 const auto
,在
1
|
|
中,类型标识符是 const auto&
。推导上面 x
, cx
以及 rx
的类型,编译器所做的事情就像是有一个模板函数一样,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
就像刚才说的一样,auto
的类型推导只有一点(一会儿会讲到)不一样之外,其他与模板类型推导完全一致。
Item 1 中根据 ParamType
的类型,把类型推导分了三种情况来处理,在 auto
类型推导时,auto
替代了 ParamType
也同样分为三种情况,
- 情况1:类型标识符是一个指针或者引用,但不是右值引用
- 情况2:类型标识符是一个全局引用
- 情况3:类型标识符既不是指针也不是引用
我们已经见过了情况1 与情况3 的例子了,
1 2 3 4 5 |
|
情况2 就像你预期的那样,
1 2 3 4 5 |
|
Item 1 中我们讨论了非引用的数组和函数是如何退化成指针的。在 auto
类型推导中也是一样的,
1 2 3 4 5 6 7 8 9 10 11 |
|
就像你看到这样,auto
类型推导就像模板类型推导一样。
仅有一种情况,他们是不一样的。我们从一个简单的例子开始,在 C++98 中,我们用 27 来初始化一个 int 变量,我们可以有下面两种写法,
1 2 |
|
C++11 统一初始化,增加了下面的写法,
1 2 |
|
总而言之,4中不同的写法的结果都是一样的,初始化了一个值为 27 的整型。
但是,就像 Item 5 中解释的一样,使用 auto
来声明类型是可以获得好处的,所有我们可以将上面的 int
全部替换为 auto
:
1 2 3 4 |
|
这 4 种写法都能够正常编译,但是却表达了不同的含义。前两种写法定义了一个值为 27 的整型。但后两种写法实际上定义了一个只有一个元素 27 的 std::initializer_list<int>
!
1 2 3 4 |
|
这是 auto
类型推导的一个特殊规则。当使用大括号初始化一个 auto
变量时,类型会被推导为 std::initializer_list
,如果类型推导不成功(比如,大括号中的某个元素类型与其他的不一致),那么将会编译失败:
1
|
|
就像上面注释中写的一样,类型推导在这种情况下会失败,但是要明白这里有两个类型推导,这是很重要的。第一个是 auto
,x5
使用了大括号来初始化,那么 auto
会被推导为 std::initializer_list
类型,但同时 std::initializer_list
是一个模板类型,模板参数 T
同样需要推导,类型推导失败产生于第二步:模板类型推导。在上面的例子中,就是由于大括号中的元素类型不一致而导致的模板类型推导失败。
auto
类型推导与模板类型推导的唯一不同在于大括号初始化。当使用大括号初始化时,auto
会推到为 std::initializer_list
类型,但对于模板类型推导来说,这会产生一个错误,
1 2 3 4 5 6 |
|
不过,如果你明确指定了 param
的类型为 std::initializer_list<T>
那么模板类型推导会推导出 T
的类型,
1 2 3 4 |
|
因此,auto
类型推导与模板类型推导唯一的不同在于,对于大括号初始化,auto
类型推导会将它推导为 std::initializer_list
,而模板类型推导不会。
也许你会想知道为什么会有这样的区别。我也曾想过,但是没有找到一个很好的解释。不过规则就是规则,你只需要记住,当使用大括号初始化 auto
类型时,它会被自动推到为 std::initializer_list
类型。如果你想要使用新的统一初始化,那么记住这一点是非常重要的。一个典型的 C++11 错误就是当你想要一个其他类型时却意外的声明了一个 std::initializer_list
类型。这也是为什么有些开发者仅仅在必要的时候才使用大括号初始化的一个原因。(我们会在 Item 7 中详细讨论)
对 C++11 来说,这就是全部了。但是对于 C++14,还有些其他内容。C++14 允许使用 auto
来表示需要被推导的函数返回值类型(详见 Item 3),C++14 的 lambda 中也允许 auto
用作参数类型。但是这些 auto
遵循的是模板类型推导,而不是 auto
类型推导。因此,一个返回值类型声明为 auto
返回一个大括号初始化时,会导致编译失败,
1 2 3 4 |
|
lambda:
1 2 3 4 5 6 |
|
需要记住的
auto
类型推导通常和模板类型推导是一致的,但是auto
类型推导会将大括号初始化推到为std::initializer_list
,而模板类型推导不会auto
在函数返回值以及 lambda 参数类型推导时,遵循模板类型推导,而不是auto
类型推导。