您的位置:首页 > 编程语言 > C语言/C++

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类型推断。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++