Effective Modern C++ 条款2 理解auto类型推断
2016-08-07 21:34
591 查看
理解auto类型推断
如果你已经阅读了条款1,那么你基本上已经知道auto的规则了,因为除了一个奇怪的例外,auto的类型推断与模板类型推断相同。auto类型推断可以被我们转换成模板类型推断。
在条款1中
template <typename T>
void f(ParamType param);
然后我们调用函数
f(expr);
在这个调用中,编译器通过expr 来推断T 的类型和ParamType 的类型。
而当我们用auto来声明定义变量时, auto 在上面的模板中扮演着T 的角色,而变量的类型则是ParamType ,很容易用以下例子来说明
auto x = 27;
在这里 x 的类型是auto,而在另一个例子中
const auto cx = x;
x 的类型是const auto,还有
const auto &rx = x;
x的类型是const auto & ,在这些例子中,编译器就像对待模板推断一样来对待auto,而相应的模板函数如下
template <typename T> void func_for_x(T param); func_for_x(27); // auto x = 27 template <typename T> void func_for_cx(const T param); func_for_cx(x); // const auto cx =x template <typename T> void func_for_rx(const T& param); func_for_rx(x); // const auto& rx = x
所以我们才说,auto类型推断和模板类型推断基本相同,只有一个例外,这个例外我们很快就会讨论到。
在条款1的模板类型推断中,根据ParamType (也就是函数中实际参数的类型)的不同分成了三种情况,而在auto类型推断中,类型标识符的类型也就是ParamType,因此也分成三种情况。
类型标识符是指针或者引用,但是不是通用引用(universal reference)
类型标识符是通用引用(universal reference)
类型标识符既不是指针也不是引用
我们已经在第一个例子中看见情况1和情况3
auto x = 27;// 情况3
const auto cx = x;// 情况3
const auto &rx = x;// 情况1
而第二种情况如下
auto&& uref1 = x;// x是int类型而且是个左值,所以uref1的类型是int&
auto&& uref2 = cx;// cx是const int类型而且是个左值,所以uref2的类型是const int&
auto&& uref3 = 27;// 27是个右值,uref3的类型是int&&
条款1中关于数组和函数的讨论,在auto中也适用
const char name[] = "R. N. Briggs";
auto arr1 = name;// arr1的类型是const char*
auto &arr2 = name;// arr2的类型是const char (&)[13]
void someFunc(int, double);
auto fun1 = someFunc;// func1的类型是void (*)(int, double)
auto &func2 = someFunc;// func2的类型是void (&)(int, double)
所以说模板类型推断和auto类型推断真的很类似。
auto类型推断有一个地方和模板类型推断不一样。
当我们想声明定义一个int类型时,在C++98有两种选择
int x1 = 27;
int x2(27);
而在C++11之后,又多了两种声明定义方式
int x3 = { 27 };
int x4{ 27 };
这四种声明定义方式的结果都是相同的,一个int类型的值为27。
在条款5中会解释用auto声明定义比用具体类型好,所以在这里我们用auto代替int,如下
auto x1 = 27;
auto x2(27);
auto x3 = { 27 };
auto x4{ 27 };
而这样编译运行后,前两个表达式中的变量是类型为int,值为27;而后两个表达式的变量类型为
std::initializer_list<int>,该序列只有一个元素值为27。
这是因为auto类型推断中的一个特殊规则,当用auto声明定义的变量用一个封闭的大括号初始化时,auto会把这个变量推断为std::initializer_list类型。如果这样的变量无法推断类型(例如大括号内有不同类型的值),那么代码会报错:
auto x5 = { 1, 2, 3.0 };// 报错,
//无法推断
std::initializer_list<T>中的T 的类型
上面的代码有两次类型推断,一次是通过括号把x5推断为
std::initilizer_list<T>,然后再用括号中的内容推断T 的类型
在auto中对于大括号初始化只有一种规则,就是把变量的类型推断为initializer_list,而相同的大括号放到模板类型推断中,类型推断会失败,而且代码也会报错:
auto x = { 11, 23, 9};// x的类型为
std::initializer_list<int>
template <typename T>
void f(T param);
f({ 11, 23, 9 });// 报错,无法推断类型T
但是,如果你在模板中把参数的类型定为
std::initializer_list<T>,,然后让编译器来推断类型T,这时候代码是正确的:
template <typename T>
void f(std::initializer_list<T> initList);
f({ 11, 23, 9 });// 此时T 的类型会被推断为int,而initList的类型是
std::initialzer_list<int>
所以,auto类型推断和模板类型推断的不同之处在于,auto会假定大括号初始化时表现为initializer_list,而模板类型推断不会这样做。
在C++14中,可以把函数的返回值类型或者lambdad中的参数用auto声明,但是这两种情况下,类型推断原则应用的是模板类型推断的规则,所以在这两种情况下使用大括号是错误的:
auto createInitList() { return { 1, 2, 3 }; // 报错 }
同理
std::vector<int> v; ... auto resetV = [&v](const auto& newValue) { v = newValue; }; ... resetV({ 1, 2, 3}); // 报错
总结
要记住的两点auto类型推断与模板类型推断基本一致,不过auto会假定大括号是一个std::initializer_list,但是模板类型推断则不会。
当一个函数返回类型是auto或者在lambda的参数中使用auto时,它们的类型推断规则是用模板类型推断的规则,而不是auto类型推断。
相关文章推荐
- Effective Modern C++ 条款1 理解模板类型推断
- 《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导
- 《Effective Modern C++》翻译--条款2: 理解auto自动类型推导
- Effective Modern C++ 条款6 当auto会推断出不合理的类型时使用显式类型初始化语法
- 《Effective Modern C++》翻译--条款2: 理解auto自动类型推导
- Effective Modern C++ 条款9补完 理解模板类型推断
- [effective modern c++][2]理解auto类型推断
- 《Effective Modern C++》翻译--条款1: 理解模板类型推导
- Effective Modern C++之Item 2 理解auto的类型推导
- [effective modern c++][1]理解模板类型推断
- Effective Modern C++:Item 2 ->弄清auto类型推断
- Effective Modern C++ Item2 理解auto类型推导
- [effective modern c++][5]与其使用显示类型推断不如使用auto
- Effective Modern C++翻译(3)-条款2:明白auto类型推导
- Effective Modern C++ 条款4 懂得如何查看已推断类型
- Effective Modern C++ :Item 1 -> 理解模板类型推断
- Effective Modern C++ 条款5 尽量用auto代替显式类型声明
- 《Effective Modern C++》翻译--条款1: 理解模板类型推导
- Effective Modern C++ 条款28 理解引用折叠
- Effective Modern C++翻译(2)-条款1:明白模板类型推导