自动类型推断

    1auto
    编译器根据表达式返回的类型在编译时,自动决定变量的类型(从c++14开始 也可以自动推断函数的返回类型)。

    1. // vector<int> v;
    2. for (vector<int>::iterator
    3. it = v.begin(),
    4. end = v.end();
    5. it != end; ++it) {
    6. // 循环体
    7. }


    从前不知道容器类型,使用模板手动传入

    1. template <typename T>
    2. void foo(const T& container)
    3. {
    4. for (typename T::const_iterator//container为const引用,取其迭代器 需要取const类型的迭代器,并且因为容器类型未知(不仅仅是容器中的元素类型未知),还需要加上typename
    5. it = v.begin(),
    6. }

    在以前如果begin返回的不是我们规定的const_iterator的嵌套类型的话,我们必须写多个foo的重载函数,比如如果foo函数要支持C数组的范围遍历的话就需要再写一个下面数组版本的重载

    1. template <typename T, std::size_t N>
    2. void foo(const T (&a)[N])
    3. {
    4. typedef const T* ptr_t;
    5. for (ptr_t it = a, end = a + N;
    6. it != end; ++it) {
    7. // 循环体
    8. }
    9. }


    现在可以直接使用auto对迭代器类型进行自动推断,并且使用c++11提供的全局begin()和end()函数—-(全局begin,end 如果传入容器得到两个迭代器,传入C数组得到数组的起始终止地址)上面两个版本的代码可以统一成

    1. template <typename T>
    2. void foo(const T& c)
    3. {
    4. using std::begin;
    5. using std::end;
    6. // 使用依赖参数查找(ADL)
    7. for (auto it = begin(c),
    8. ite = end(c);
    9. it != ite; ++it) {
    10. // 循环体
    11. }
    12. }

    auto推导规则类似于函数模板参数的推导规则,
    相当于把 auto 替换为模板参数的结果。举具体的例子:
    将expr看成是传入模板函数的实参,模板函数根据实参对其形参a类型进行匹配
    auto a = expr; 意味着用 expr 去匹配一个假想的 template f(T a) 函数模板,结果为值类型。 根据实参expr类型去推导T
    auto a这么写a只有可能被推导为值类型—-即T a

    const auto& a = expr; 意味着用 expr 去匹配一个假想的 template f(const T& a) 函数模板,结果为常左值引用类型。
    const auto& == const T&
    const auto& a这么写a只有可能被推导为常左值引用类型—-即const T&

    auto&& a = expr; 意味着用 expr 去匹配一个假想的 template f(T&& a) 函数模板,根据万能引用和引用折叠规则,结果是一个跟 expr 值类别相同的引用类型。
    当实参expr为左值,设其removere_reference后的类型为X则 T&& == auto&&==X&
    当实参expr为右值,设其removere_reference后的类型为X则 T&& == auto&&==X&&
    auto&& a,a根据express的值类别,推导为左值引用或右值引用类型

    expr(等号右边)为const 和引用结合时的类型时,auto 的推导将保留表达式的 const 类型

    auto可以和指针,引用、const(auto会忽略顶层const)、volatile结合起来使用
    规则1:auto&/auto*或者auto x =&i(此时x一定会被推导为指针类型 此时x会保留i的cv限定),推导结果“保留=右侧的底层const/volatile属性”
    规则2:auto不和指针/引用结合 且等号右边不是对象取地址,推导结果“删除=右侧的底层const/volatile属性”
    auto与顶层和底层const的推导

    1. int *const a = &x;// 这个const是顶层const
    2. auto b = a;//b--int*不保留const auto直接忽略=右边的顶层const
    3. const int *a = &x;//这个const是底层const
    4. auto b = a;//b--const int*保留const


    关于auto和顶层const这里举两个例子
    1

    1. int *p ;
    2. //下面两种写法都被认为是显示地添加顶层const的写法
    3. //p1,p2最终推导出的类型都是int* const
    4. auto *const p1 = p;
    5. const auto p2 = p;//p为int*指针,auto的类型为指针类型,
    6. //在指针类型前加上const限定就变为常指针(顶层const) 顶层const必须这样显示地指定

    编译器认为对auto,以推断出是指针类型的情况下,做const修饰,就是添加顶层const限定(因为底层const auto不会忽略,可以直接从=右边的变量继承过来)

    2

    1. const int *p ;
    2. //p4,p5最终推导出的类型都是const int* const
    3. //因为p本身已经有const
    4. auto *const p4 = p;
    5. const auto p5 = p;//指针类型出现const冗余 编译器会为顶层和底层都加上const修饰

    auto无法自动推导引用类型,引用类型必须自己执行—auto&。但是auto可以自动推导指针类型—int a=1;int b=&a;auto c=b;//c type==int。 当我们在写的时候可能不小心出现符号的冗余比如 auto c = b;//auto允许的冗余,并会自动去掉的冗余,c type==int*

    (1)auto的作用是让编译器自动推断变量的类型,而不需要显式指定类型。这种隐式类型的推导发生在编译期。
    (2)auto并不能代表实际的类型声明,只是一个类型声明的“占位符”
    (3)auto声明的变量必须马上初始化,以让编译器推断出它的实际类型。

    auto的限制
    (1)auto不能用于函数参数(不能将函数参数类型定义为auto)。如果需要泛型参数,可借助于模板
    (2)auto不能用于非静态成员变量(不能将成员变量的类型定义为auto)
    (3)auto无法定义数组(数组的类型不能用auto声明 编译器无法通过数组元素类型反推数组类型)
    (4)auto无法推导模板参数,即实例化模板参数类型时不能用auto( 比如vector a;vector b = a;//错误 无法推导)

    与函数模板类型自动推导时同理 f(T)在自动推导时(不是手动<实例化类型>的情况),T只会被推导为值类型,f(const T&)—const T&这个整体类型一定只会是常左值引用,f(T&&)按万能引用的规则来推导T,T&&整体是个左值引用或右值引用。
    fun(PT a); fun(expr);根据PT和expr的类型可以将推导出PT,以及T
    PT为引用或指针
    PT为T&规定了实参必须是左值,当PT为const T&实参可以是个右值
    8 易用性改进1:自动类型推断和初始化 1 auto,decltype - 图1
    T& int&& int& int
    当PT为T&,expr为int&& x,首先根据字符匹配T &==int& &,T会被推导为int&。当把int&作为T带回会发生引用折叠PT被推导为int&,再回改T==int。

    PT为值类型
    8 易用性改进1:自动类型推断和初始化 1 auto,decltype - 图2
    expr为数组
    8 易用性改进1:自动类型推断和初始化 1 auto,decltype - 图3
    expr为函数名
    8 易用性改进1:自动类型推断和初始化 1 auto,decltype - 图4

    PT是万能引用类型的情况,以前笔记里都讲过了这里就不讲了 这里要强调一下
    T&& const int const int& const int&
    T&& const int& const int& const int&
    T&& const int&&(值类别为右值引用的左值) const int& const int&
    上面的为expr为左值的情况
    万能引用T&&当传入的值类别为左值时,推导T类型时会将传入变量的类型拿来再加上&(可能发生引用折叠)作为T类型。 当传入的值类别为右值时推导T类型时会将传入变量的类型直接作为T类型。

    再补充一点
    const来修饰返回的指针或引用,保护指针指向的内容或引用的内容不被修改,也常用于运算符重载。归根究底就是使得函数调用表达式不能作为左值

    expr都是左值的情况,传入一个左值const int, T直接被推导为const int+&(因为这是一个左值类型,T&&发生引用折叠被推导为const int&类型。传入一个左值const int&,T被推导为const int&+&(因为这是一个左值类型发生引用折叠变为const int&,T&&发生引用折叠被推导为const int&类型。传入一个左值const int&&,T被推导为const int&&+&(因为这是一个左值)类型发生引用折叠变为const int&,T&&发生引用折叠被推导为const int&类型。

    fun(T&& a) —- 根据传入的x是左值还是右值来推导T&&
    上面的3个例子也说明了auto的不便利之处
    你需要在写下 auto 时就决定你写下的是个引用类型还是值类型 以及确定类型的cv限定符。根据类型推导规则,auto 是值类型,auto& 是左值引用类型,auto&& 是转发引用(可以是左值引用,也可以是右值引用)。使用 auto 不能通用地根据表达式类型来决定返回值的类型。
    auto 必须程序员自己指明以及确定类型的cv限定符—const auto,否则都会将=右边表达式的cv限定符去掉
    使用auto 时就意味着,程序员此时确定地知道要声明的是个引用类型还是值类型、左值引用还是转发引用 以及确定类型的cv限定符;这通常意味着希望改变类型;使用decltype(auto)意味着完全根据表达式决定返回类型;这通常意味着不希望改变类型。

    2decltype https://www.cnblogs.com/5iedu/p/6931617.html
    decltype可以在编译期推导出一个变量或表达式的结果类型(但不会真正计算表达式的值),并且使用这个结果定义新的变量。
    decltype用于获得一个表达式的类型,它有两个基本用法
    decltype(变量名) 获得变量(这里的变量包含成员变量)的精确类型(包括cv限定符也一样),因为用的是变量名,它一定是有名的,如果变量是引用类型,在decltype(变量名)后得到的只有可能是左值引用类型—-注意数组取索引不是变量名,比如arr[3]不是变量名

    decltype(表达式) (注意变量名不是表达式,函数名,变量名做op是表达式,特别为变量名加括号(变量名)是表达式),可以获得表达式的引用类型,除了表达式结果为纯右值(亡值move也不行 必须是纯右值)时decltype(表达式)结果为值类型(纯右值 只有当这个纯右值是类类型 cv限定符会保留,一般类型会丢失cv限定符)表达式结果为亡值,decltype(表达式)结果为右值引用

    这里再细讲一下当表达式是函数调用(注意是调用())时,如果函数返回不是纯右值则decltype(表达式)和函数的返回值类型一致,如果返回的是纯右值 如果返回类型是类类型则保留cv限定,否则是普通类型不保留cv限定。——即decltype在函数或表达式返回除了普通类型的纯右值外都是保留函数返回值的cv限定符的。

    但是如果expr是个对象的成员变量,比如对象为const X x; 里面有个成员变量int i。decltype(x.i)—-int(不会因为x为const而变为const int),i的类型是不会保留其对象的cv限定符的—-decltype成员变量的类型与对象的类型无关,在定义成员变量时是什么类型拿decltype的结果就是什么类型但是如果const X x;decltype((x.i))—多了一个括号,从成员变量访问变为表达式,decltype的结果变为const int&,保留了对象的const,并且因为表达式返回的是左值所以是还是个引用类型

    冗余
    auto忽略顶层const(除非显示写出),decltype保留顶层const
    当decltype出现符号冗余时,不会像auto一样忽略而是保留比如

    1. int*p; decltype(p)* pp;//pp type == int**
    2. //但是引用冗余并不会被认为是右值引用
    3. int &b = a;
    4. decltype(b) c=a; //c:int&
    5. decltype(b)& c=a;//c:int&

    对于非指针类型的const冗余都会忽略,const decltype(p),decltype(p) const都会被推导为顶层const指针类型(因为p是指针,指针+const为指针常量)intconst类型。
    decltype会直接忽略顶层const的冗余,int
    const p; const decltype(p) == decltype(p) const ==intconst。只有const int p; decltype(p) const == const decltype(p) == const int const不算冗余成功添加上了顶层cosnt。总结对于指针类型的const冗余—const decltype(p),decltype(p) const,如果p是顶层const指针(指针常量 指向不可改),const冗余忽略。如果p是底层const(常量指针)则多加上的const被认为是添加顶层const
    (1).如果e是一个没有带括号的标记符表达式或者类成员访问表达式,那么的decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译错误。
    (2).否则 ,假设e的类型是T,如果e是一个将亡值,那么decltype(e)为T&&
    int && RvalRef();//函数返回右值引用 如果这个右值引用(函数内的某个右值变量本身)没被绑定到一个有名变量上,那么这个表达式返回的就是一个亡值decltype (RvalRef()) x;//x类型被判定为int&&
    decltype (move(x)) y;//x有名是左值 move后返回的是一个亡值 所以y的类型为int&&

    (3).否则,假设e的类型是T,如果e是一个左值,那么decltype(e)为T&。
    (4).否则,假设e的类型是T,则decltype(e)为T。
    标记符指的是除去关键字、字面量等编译器需要使用的标记之外的程序员自己定义的标记,而单个标记符对应的表达式即为标记符表达式。

    decltype(a) 会获得 int(因为 a 是 int)。
    decltype((a)) 会获得 int&(因为(a)为一个表达式,这个表达式的返回类型 a 是 lvalue)。
    decltype(a + a) 会获得 int(因为 a + a 是 prvalue)。
    小补充decltype(静态类成员) = 会删除static

    在写下 auto 时就决定你写下的是个引用类型还是值类型。根据类型推导规则,auto 是值类型,auto& 是左值引用类型,auto&& 是转发引用(可以是左值引用,也可以是右值引用)。使用 auto 不能通用地根据表达式类型来决定返回值的类型。不过,decltype(expr) 既可以是值类型,也可以是引用类型。
    我们可以这么写
    decltype(expr) a = expr;//保证a的类型和expr类型一致
    但是当expr较长时 decltype(expr) a = expr 这么写会很冗长
    为此c++14引入decltype(auto)语法
    decltype(expr) a = expr;等同于
    decltype(auto) a = expr;
    decltype可以形别推导,例如decltype(auto) 即保证形别与返回值或者=右边的值得类型完全一致!因为使用函数模板或者auto推导的时候,会去除引用&,decltype可以恢复

    这种代码主要用在通用的转发函数模板中:你可能根本不知道你调用的函数是不是会返回一个引用。这时使用这种语法就会方便很多。

    **从c++14引入函数返回值类型判断,函数返回值类型可以用auto(以及含auto引用的标识符,auto&,auto&&,auto
    )或decltype(auto)来声明,auto or decltype(函数) xxx=函数。和这个形式相关的还有一个语法,后置返回值类型声明**

    1. decltype(xxxx) foo(参数)
    2. {
    3. // 函数体
    4. }
    5. //但注意下面这个例子 因为编译器是从左向右读入符号,在decltype时,
    6. //此时t/u并没有读入,因此不能推导t+u的类型
    7. template<typename R, typename T, typename U>
    8. decltype(t + u) add(T t, U u)
    9. {
    10. return t + u;
    11. }
    12. decltype(T+ U) add(T t, U u);//ok
    13. or
    14. auto foo(参数) -> decltype(xxxx) //返回类型后置,追踪返回类型
    15. {
    16. // 函数体
    17. }


    通常在返回类型比较复杂、特别是返回类型跟参数类型有某种推导关系时会用这种语法
    使用auto 时就意味着,程序员此时确定地知道要声明的是个引用类型还是值类型、左值引用还是转发引用 以及确定类型的cv限定符;这通常意味着希望改变类型;使用decltype(auto)意味着完全根据表达式决定返回类型;这通常意味着不希望改变类型。
    decltype(expr)可以保留expr的cv限定符以及引用类型(一些不能保留的特例在上面讲过了)(auto 必须程序员自己指明以及确定类型的cv限定符—const auto,否则都会将=右边表达式的cv限定符去掉)。
    小总结一下auto和decltype的cv继承和冗余处理以及对顶层const的处理

    差异 auto decltype
    cv限定 auto&或auto*或auto=&x会保留v和底层const(常量指针),其他情况抛弃cv限定,除非自己显示定义const auto 函数或表达式返回普通类型(非类类型)的纯右值,会抛弃cv限定,其他情况保留。—-对于成员变量的cv看上面笔记
    顶层const 抛弃=右边指针的顶层const,除非自己显示定义const auto或auto* const。看上面auto显示添加顶层const的例子(const冗余)
    保留顶层cosnt。对于指针类型的const冗余—const decltype(p),decltype(p) const,如果p是顶层const指针,const冗余忽略。如果p是底层const(常量指针)则多加上的const被认为是添加顶层const。
    引用
    int& b = a;
    auto的引用必须显示定义auto&或右引auto&&,否则auto一律推导为值类型
    auto会省略冗余的&
    auto& c =b;//ctype int&
    decltype自动推导保留引用,
    decltype(b)& c—ctype为int&。引用冗余会省略,不会推导为右值引用。
    指针
    int*p = &a;
    auto的冗余会省略,不会推导为**二级指针,auto c =p;推导ctype为int。auto c=&p;auto c=&p;都推导为二级指针 decltype(p)冗余不会被忽略,推导为int**二级指针


    在应用的代码,一般写 auto 更安全。如果你是写通用的模板代码,那可能就需要写 decltype(auto) 了。但你得仔细考虑一下对象的生命期问题,确保不会返回一个过期的引用。