需要记住:除了一个奇特的情况例外以外,auto型别推导就是模板型别推导
template<typename T>
void f(ParamType param);
f(expr); //以某表达式调用f
auto x = 27; //auto扮演了上述T的角色,变量的型别修饰词则扮演了ParamType的角色,
const auto cx = x;
const auto& rx = x;
// 对于以上三条,可以模拟出一个概念模板去理解auto的推导过程
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的型别
和条款1类似,也分为三种情况处理auto型别推导:
情况1. 型别饰词是指针或引用,但不是万能引用
情况2. 型别饰词是万能引用
情况3. 型别饰词既不是指针又不是引用
上述情况1和情况3已经讨论过,重点说一下情况2,如下例子:
auto&& uref1 = x; //x的型别是int,且是左值,所以uref1的型别类型是int&
auto&& uref2 = cx; //cx的型别是const int,且是左值,所以uref2的型别类型是const int&
auto&& uref3 = 27; //27的型别是int,且是右值,所以uref3的型别类型是int&&
对于数组和函数而言:
const char name[] = "Adam Xiao"; //name的型别是const char[13]
auto arr1 = name; //arr1的型别是const char*
auto& arr2 = name; //arr2的型别是const char(&)[13]
void someFunc(int, double); //someFunc是个函数,型别是void(int, double)
auto func1 = someFunc; //func1的型别是void(*)(int, double)
auto& func2 = someFunc; //func2的型别是void(&)(int, double)
那么特例来了
对于赋值构造的时候,会出现一些不一样的情况。对于C++98的时候,可以选择如下方式进行变量初始化:
int x1 = 27;
int x2(27);
而在C++11中,为了支持统一初始化 uniform initialization
,新增了下面的语法选项:
int x3 = {27};
int x4{27};
一共有四种语法,然而结果却殊途同归,得到一个值为27的int。
将这四种初始化语法换成auto
情况则会出现一些不同点:
auto x1 = 27; //型别是int,值是27
auto x2(27); //型别是int,值是27
auto x3 = {27}; //型别是std::initializer_list<int>,值是{27}
auto x4{27}; //型别是std::initializer_list<int>,值是{27}
这里出现奇特的原因是因为以下规定:
当用于auto声明变量的初始化表达式是使用大括号括起时,推导所得的型别就属于
std::initializer_list
在以上奇特规定的限制下,如下报错就不奇怪了:
auto x5 = {1, 2, 3.0}; //错误! 推导不出std::initializer_list<T>中的T
对比Item 1去看,上述这个奇特的规定就是auto型别推导和模板型别推导的唯一不同之处
。而这个不同之处存在的根因,实际上是因为auto会假定用大括号括起的初始化表达式代表一个std::initializer_list,但模板型别推导却不会
。代码例子如下:
auto x = {11, 23, 9}; //x的型别是std::initializer_list<int>
template<typename T> //带有形参的模板
void f(T param); //与x的声明等价的声明式
f({11, 23, 9}); //错误!无法推导T的型别
要使得模板推导能够正常工作,那么必须写成这样:
template<typename T>
void f(std::initializer_list<T> initList);
f({11, 23, 9}); //T的型别推导为int,从而initList的型别是std::initializer_list<int>
对于以上情况,会得出一个常见的很多程序猿的习惯:
很多程序猿都只在必要的时候才会使用大括号括起的初始化表达式
对于C++14来说,额外有几点要说明,C++14比C++11额外支持了auto
可以当做返回值。那么根据上述特例的以下代码是无法通过编译的:
//大括号作为返回值的时候,auto无法推导
auto creatInitList()
{
return {1,2,3}; //错误,无法为{1,2,3}完成型别推导
}
//大括号作为入参的时候,auto无法推导
std::vector<int> v;
...
auto resetV = [&v](const auto& newValue){v = newValue;}; //C++14
...
resetV({1,2,3}); //错误,无法为{1,2,3}完成型别推导
要点速记 |
---|
1. 在一般情况下,auto型别推导和模板型别推导是一模一样的。但是auto型别推导会将大括号括起的初始化表达式理解为std::initializer_list,但模板型别推导却不会有这样的理解。 |
2. 在函数返回值或lambda表达式中的形参使用auto,意思是用模板型别推导而非auto型别推导。 |