条款2:理解 auto 型别推导
首先我们抛出这一讲的核心思想: auto 类型推导几乎就是模板类型推导, 除了稍后会介绍到的唯一区别之外
两者概念性的转换
这种对应关系可以通过一个概念性的转换来理解。例如, 对于前一讲介绍的函数模板调用:
1 | template<typename T> |
一个使用auto声明的变量可以被看作是这个模式的变体,其中auto扮演了模板中的T,而变量的类型修饰符(如const、&等)和T一起则扮演了ParamType 。
例如,以下auto声明:
1 | auto x = 27; |
1 | // 概念上为推导 x 的类型可以理解为存在以下模板 |
auto的三种推导情况
因为auto类型推导与模板类型推导机制相同,条款1中划分的三种推导情况也完全适用于auto。
- 情况1:类型修饰符是指针或引用(但非万能引用)
此时推导规则与模板相同,初始化表达式的引用性和const属性会被保留。
1 | const auto& rx = x; // rx 是一个非万能引用 [cite: 278] |
- 情况2:类型修饰符是万能引用
当使用auto&&时,左值和右值初始化物会被区别对待,推导出不同的类型。
1 | auto&& uref1 = x; // x 是 int 且是左值, uref1 类型为 int& [cite: 279] |
- 情况3:类型修饰符既非指针也非引用
此时推导规则也与模板相同,初始化表达式的引用性、const和volatile属性都会被忽略。
1 | auto x = 27; // 情况3 (x既非指针也非引用) [cite: 276] |
同样地,数组和函数名在auto类型推导中也会退化成指针,除非auto被声明为引用。
1 | const char name[] = "R. N. Briggs"; // name 的类别是 const char[13] |
唯一的例外:大括号初始化表达式
auto类型推导和模板类型推导真正的唯一区别在于它们如何处理用大括号括起来的初始化表达式。
正如在条款7中提到的, 在C++11中,有多种语法可以初始化一个int:
1 | int x1 = 27; |
当把int替换为auto时,前两种写法的行为符合预期,变量被推导为int。然而,后两种使用大括号的写法,会触发一条针对auto的特殊推导规则:当用于auto声明的变量的初始化表达式是用大括号括起时,推导所得的类型就是std::initializer_list 。
1 | auto x1 = 27; // 类型是 int [cite: 291] |
这个特殊规则是auto独有的。如果将同样的大括号初始化物传递给一个函数模板,类型推导会失败,代码将无法通过编译; 而auto则可以
1 | auto x = {11, 23, 9}; // x 的类型是 std::initializer_list<int> [cite: 300] |
不过需要注意的是,这条关于大括号的特殊规则仅适用于auto变量声明。在C++14中,当auto被用于推导函数返回值或用于lambda表达式的形参时,它遵循的是模板类型推导的规则,而非auto的特殊规则 。
因此,一个返回大括号初始化表达式的函数将无法通过编译,因为它遵循的是模板类型推导,而模板类型推导无法处理这种情况 。
1 | // C++14 |