首先我们需要明确的是auto型别推导与模板型别推导的运作原理几乎完全相同,如下所见:
auto x = 27; // x型别为int
const auto cx = x; // cx型别为const int
const auto& rx = x; // rx型别为const int&
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[] = "this is test"; // name型别为const char[13]
auto arr1 = name; // arr1型别为const char*
auto& arr2 = name; // name型别为const char(&)[13]
void somefunc(int,double); // somefunc是一个函数,型别为 void(int,double)
auto func1 = somefunc; // func1类型为void(*)(int,double)
auto& func1 = somefunc; // func2类型为void(&)(int,double)
我们知道,C++11为了支持统一初始化,增加了大括号初始化表达式:
auto x1 = {27}; // x1为含有单个值27的std::initializer_list<int>类型变量
auto x2{27}; // 同上
由上面代码可以看出,对于大括号初始化表达式,auto会将其推导为一个std::initializer_list<T>的类型,而模板型别推导则无法完成此类推导,从而失败。
auto x = {27,11,2,9}; // x被推导为std::initializer_list<int>类型
template<typename T>
void f(T param);
f({27,11,2,9}); // 错误,无法推导出T的类型
请注意,虽然模板推导识别不了大括号初始化表达式类型,但若你指定该模板中的param为std::initializer_list<T>类型,则,此时再传入大括号初始化表达式,就可以正常识别出T的类型了:
template<typename T>
void f(std::initializer_list<T> param);
f({27,11,2,9}); // 此时,T被推导为int,param为std::initializer_list<int>
最后,我们还应该知道,在C++14中,允许使用auto来说明函数返回值需要推导,而且C++14中的lambda表达式的形参声明中也可以使用auto进行修饰。但是需要注意的时,这些场景下的auto用法使用的时模板型别推导,而非auto型别推导。
所以,带有auto返回值的函数若要返回一个大括号括起来的初始化表达式,是通不过编译的:
auto func() {
return {1, 2, 3}; // 编译报错,无法完成{1, 2, 3}的型别推导
}
同理,使用auto来指定C++14中的lambda表达式形参型别时,也不能使用大括号括起来的初始化表达式来做实参:
std::vector<int> v;
auto resetV = [&v](const auto& new_value) {v = new_value};
resetV({1,2,3}); // 错误,无法完成{1,2,3}型别推导