• 现代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的类型,T为int,ParamType为const int&
    

    继续回顾上一篇帖子的内容,基于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类型推断。
  • 相关阅读:
    SQL2012远程连接到SQL2008时的问题:已成功与服务器建立连接,但在登陆过程中发生错误。
    常用正则表达式
    ASP.NET不拖控件教程(1)-认识JSON
    ASP.NET服务器控件对应的HTML标签
    Apache Ranger 1.1.0源码导入IDEA并运行调试security-admin web模块
    【JDK源码分析】线程池ThreadPoolExecutor原理解析
    【JDK源码分析】同步工具Exchanger,它的内部实现原理你看懂了吗?
    【JDK源码分析】并发包同步工具Semaphore
    【JDK源码分析】并发包同步工具CountDownLatch
    【JDK源码分析】并发包同步工具CyclicBarrier
  • 原文地址:https://www.cnblogs.com/harlanc/p/10628321.html
Copyright © 2020-2023  润新知