Effective Modern C++ 条款2 理解auto类型推断

来源:互联网 发布:python 3程序开发指南 编辑:程序博客网 时间:2024/04/30 16:25

理解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  = 27template <typename T>void func_for_cx(const T param);func_for_cx(x);   // const auto cx =xtemplate <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类型推断。
0 0