现代C++之理解auto类型推断

理解auto类型推断

上一篇帖子中讲述了模板类型推断,我们知道auto的实现原理是基于模板类型推断的,回顾一下模板类型推断:

template <typename T>
void f(ParamType param);

使用下面的函数调用:

f(expr);

我们看到模板类型推断过程涉及到了模板template、函数f以及参数(包括模板参数和函数参数),调用f的时候,编译器会推断T和ParamType的类型。auto的实现和这三个部分是有着对应关系的。当使用auto声明一个变量,auto关键字扮演的是模板类型推断中T的角色,而类型说明符扮演的是ParamType的角色。看下面的例子:

auto x = 27;  //类型说明符就是auto自己
const auto cx =x; //类型说明符为const auto
const auto& rx =x;//类型说明符为const auto&

编译器使用auto对上面的类型进行推断就如同使用了下面的模板类型推断:

template<typename T>
void func_for_x(T param); //ParamType即非引用也非指针
func_for_x(27); // 推断x的类型,T为int ,ParamType 为 int

template<typename T>
void func_for_cx(const T param); //ParamType即非引用也非指针
func_for_cx(x); //用于推断cx的类型,T为int,ParamType为 const int

template<typename T>
void func_for_rx(const T& param);//ParamType为引用
func_for_rx(x); // 用于推断rx的类型

继续回顾上一篇帖子的内容,基于ParamType的三种形式,模板类型推断也对应着三种不同情况。而auto的类型说明符扮演的是ParamType,因此使用auto进行变量声明,也会有三种情况:

  • 类型说明符是指针或者引用类型,但不是universal reference
  • 类型说明符是universal reference。
  • 类型说明符即非指针也非引用。

上面举的例子是第一种和第三种情况:

auto x = 27; //case 3 x类型被推断为int
const auto cx = x; //case 3  cx被推断为 const int
const auto &rx = x; //case 1 rx被推断为const int &

举一个情况2的例子:

auto&& uref1 = x; //x为左值,uref1被推断为左值引用
auto&& uref2 = cx; // cx  const int 左值,uref2被推断为const int &
auto&& uref3 = 27; // 27 为 int 右值,uref3被推断为 int &&

上篇帖子介绍了对于模板中的非引用ParamType,传入函数或者数组实参的时候会退化为指针的情况(而使用引用ParamType的时候,数组实参会被推断为指向数组的引用),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 func1 = someFunc; // func1的 类型为 void (*)(int, double)
auto& func2 = someFunc; // func2的类型为 void (&)(int, double)

上面介绍的都是auto和模板类型推断使用原理相同的部分,下面说的不一样的。

C++98中初始化一个Int有两种方式:

int x1=27;
int x1(27);

在C++11中,支持统一初始化(uniform initialization):

int x3 = {27};
int x3{27};

四种语法形式的结果只有一个,初始化一个Int值为27。这里我们将都使用auto进行初始化:

 auto x1 = 27;
 auto x2(27);
 auto x3 = {27};
 auto x4{27}; 

上面的四句话都能编译通过,但并没有和原来的四种形式意义完全一致。前面两个是一样的,后面两句话声明的变量类型是std::initializer_list,其中包含了单个元素,值为27。

 auto x1 = 27; //x1为int,值为27
 auto x2(27);//同上
 auto x3 = {27};//x3为 std::initializer_list<int>,值为{27}
 auto x4{27}; //同上
 

这里就用到了一个对于auto的特殊类型推断规则:当用大括号括起来的值对auto变量进行初始化的时候(叫做统一初始化式),变量类型会被推断为 std::initializer_list。如果不能够推断成此类型(比如,大括号中的值不是同一类型),编译会出错:

 auto x5 = { 1, 2, 3.0 }; // error! 类型不一致,不能将推断为std::initializer_list<T>
 

这里会发生两种类型推断,一种是将统一初始化式推断为std::initializer_list ,而std::initializer_list本身也是一个类型为T的模板,因此会根据统一初始化式中的实参对T进行模板类型推断,这是第二种类型推断。上面的类型推断会失败是因为第二种类型推断会失败。

对统一初始化式的处理的不一致是auto和模板类型推断的唯一区别。使用统一初始化式对auto变量初始化会将其推断为std::initializer_list,但是模板类型推断不会这么做:

auto x = { 11, 23, 9 }; // x的类型为 std::initializer_list<int>

template<typename T> // 和auto x等同的模板类型推断
void f(T param); 

f({ 11, 23, 9 }); // 错误!这里不能推断T的类型。

如果要达到auto的效果,得按照下面的方式来做:

template<typename T>
void f(std::initializer_list<T> initList);
f({ 11, 23, 9 }); // T被推断为int, initList 的类型为 std::initializer_list<int>

在C++11中使用auto时,这里比较容易出错,你本来想声明别的变量,最终却将其声明成了一个 std::initializer_list。因此,要谨慎使用统一初始化

在C++14中,允许将auto作为函数返回值,也可以用其修饰lambda表达式中的参数。但是这些auto使用的都是模板类型推断,而不是auto类型推断,因此一个函数返回值为auto 类型时,返回统一初始化式的值会出错:

auto createInitList()
{
    return { 1, 2, 3 }; // 错误!不能推断{1,2,3}
}

下面的方式是对的:

std::initializer_list<int> createInitList()
{
    return { 1, 2, 3 }; //
}

最后总结一下:

  • 模板类型推断是auto的基础,auto关键字扮演了模板类型推断中的T,而类型说明符扮演的是ParamType。
  • 对于模板类型推断和auto类型推断,大多数场景下推断规则相通,有一种特殊情况,就是统一初始化式。
  • C++14中使用auto可以作为函数返回值,也可以作为lambda表达式的参数修饰符,但需要注意,这里的auto使用的是模板类型推断,而不是auto类型推断。

原文地址:https://www.cnblogs.com/harlanc/p/10628321.html

时间: 2024-10-29 05:08:14

现代C++之理解auto类型推断的相关文章

现代C++之理解模板类型推断(template type deduction)

理解模板类型推断(template type deduction) 我们往往不能理解一个复杂的系统是如何运作的,但是却知道这个系统能够做什么.C++的模板类型推断便是如此,把参数传递到模板函数往往能让程序员得到满意的结果,但是却不能够比较清晰的描述其中的推断过程.模板类型推断是现代C++中被广泛使用的关键字auto的基础.当在auto上下文中使用模板类型推断的时候,它不会像应用在模板中那么直观,所以理解模板类型推断是如何在auto中运作的就很重要了. 下面将详细讨论.看下面的伪代码: templ

《Effective Modern C++》翻译--条款2: 理解auto自动类型推导

条款2: 理解auto自动类型推导 如果你已经读过条款1关于模板类型推导的内容,那么你几乎已经知道了关于auto类型推导的全部.至于为什么auto类型推导就是模板类型推导只有一个地方感到好奇.那是什么呢?即模板类型推导包括了模板.函数和参数,而auto类型推断不用与这些打交道. 这当然是真的,但是没关系.模板类型推导和auto自动类型推导是直接匹配的.从字面上看,就是从一个算法转换到另一个算法而已. 在条款1中,阐述模板类型推导采用的是常规的函数模板: template<typename T>

《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导

条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇.那是什么呢?即模板类型推导包含了模板.函数和參数,而auto类型判断不用与这些打交道. 这当然是真的.可是没关系. 模板类型推导和auto自己主动类型推导是直接匹配的. 从字面上看,就是从一个算法转换到还有一个算法而已. 在条款1中.阐述模板类型推导採用的是常规的函数模板: template<ty

《C++ Primer》学习 之 编译器推断auto类型

/* <C++ Primer>学习 之 编译器推断auto类型 书P61-P62 重点在于程序中的注释,方便以后复习. */ 1 #include <iostream> 2 #include <cmath> 3 #include <string> 4 using namespace std; 5 6 int main() 7 { 8 int i = 0, &r = i; 9 auto a = r; // a : int 10 11 const int

类型别名、auto类型说明符和decltype类型说明符初探

类型别名 类型别名顾名思义是某种类型的另一个名字,常用于简化类型,易于理解和使用. 传统方法是使用关键字 typedef .新标准规定使用别名声明(alias declaration)来定义类型别名. using zhengxing = int; 如果某个类型别名指代的是复合类型或常量,那么把它用到声明语句中往往会让人理解出错. typedef char *cstring;//cstring 是 char* 的别名 const cstring cstr = 0;//cstr 是指向 char 的

C++11新特性:自动类型推断和类型获取

声明:本文是在Alex Allain的文章http://www.cprogramming.com/c++11/c++11-auto-decltype-return-value-after-function.html的基础上写成的. 加入了很多个人的理解,不是翻译. 转载请注明出处 http://blog.csdn.net/srzhz/article/details/7934483 自动类型推断 当编译器能够在一个变量的声明时候就推断出它的类型,那么你就能够用auto关键字来作为他们的类型: [c

《Effective Modern C++》翻译--条款1: 理解模板类型推导

北京2016年1月9日13:47:17 开始第一章的翻译. 第一章名为 类型推断 分为四个条款: 1理解模板类型推导 2理解auto自动类型推导 3理解decltype操作符 4如何对待推导的类型 第一章 类型推导 C++98有一套单一的类型推导的规则用来推导函数模板.C++11轻微的修改了这些规则并且增加了两个推导规则,一个用于auto,一个用于decltype.接着C++14扩展了auto和decltype可以使用的语境.类型推导的普遍应用将程序员从必须拼写那些显然多余的类型中解放了出来,它

C++ auto类型说明符

本系列文章由 @yhl_leo 出品,转载请注明出处. 文章链接: http://blog.csdn.net/yhl_leo/article/details/50864612 编程时常常需要把表达式的值赋给变量,这就要求在声明变量的时候清楚知道表达式的类型.然而要做到这一点并非那么容易,有时候甚至根本做不到.为了解决这个问题,C++11标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型. 与原来那些只对应一种特定类型的说明符不同,auto让编译器通过初值来推算变量类型.显

[C++]高效使用c++11--理解auto类型推导

推导类型 1. 理解类型推导 auto的推导方式和template是一样的,所以我们首先来介绍template是如何推导类型的. template <typename T> void f(const T& orig) { cout << __PRETTY_FUNCTION__ << endl; cout << typeid (orig).name() << endl; cout << typeid (T).name() <