• 第4课 decltype类型推导


    第4课 decltype类型推导

    一、decltype类型推导

    (一)语法:

        1、语法:decltype(expr),其中的expr为变量(实体)或表达式

        2、说明:

            ①decltype用于获取变量的类型,或表达式结果的类型或值类型。decltype推导过程是在编译期完成的,并且不会真正计算表达式的值

            ②decltype会精确地推导出表达式定义本身的类型,不会像auto在某些情况下舍弃引用和cv限定符。

            ③与auto不同,decltype(expr)可根据表达式直接推导出类型本身,而无须要求变量一定要初始化。

    (二)decltype推导的四条规则

        ①如果expr是一个没有带括号的标记符表达式如局部变量名、命名空间作用域变量、函数参数等或者类成员访问表达式注意,静态数据成员按规则③推导,那么的decltype(expr)就是expr所命名的实体的类型。此外,如果expr是一个被重载的函数,则会导致编译错误。

        ②否则,如果expr是一个将亡值(xvalue),假设expr的类型是T,那么decltype(expr)为T&&。(说明:xvalue如std::move返回值、T&&函数返回值)

        ③否则,如果expr是一个左值(lvalue),假设expr的类型是T,那么decltype(expr)为T&。

        ④否则,如果expr为纯右值(pvalue),假设expr的类型是T,则decltype(expr)为T对于纯右值而言,只有类类型可以保留cv限定符,其它类型则会丢失cv限定。(说明:纯右值如非字符串字面常量、非引用返回的对象、表达式产生的临时对象)

    (三)注意事项:

        (1)标记符表达式:即除去关键字、字面量等编译器所需要使用的标记之外的程序员自定义的标记(token)都是标记符。而单个标记符对应的表达式就是标记符表达式。如int arr[4];那么arr是一个标记符表达式arr[3],arr[3]+0等都不是标记符表达式

        (2)上述“expr所命名的实体的类型”和“expr的类型”是不完全相同的两个概念。在类成员访问表达式(如E1.E2或E1->E2)中,expr所命名的实体的类型即为E2的“声明类型”,而expr的类型指整个表达式E1.E2求值结果的类型

            ①如果E2为静态数据成员,表达式E1.E2的结果始终是左值。decltype(E1.E2)按规则③而不是规则①推导。

            ②如果E2是个引用类型(如T&或T&&),decltype(E1.E2)指E2实体的声明类型(T&或T&&)。而整个表达式E1.E2结果则为T类型的左值,即代表E2引用所指的对象或函数。

            ③若E2为非引用类型:当E1为左值时,E1.E2整个表达式为左值。而如果E1为右值,则整个表达式为右值类型。(E2为静态数据成员例外,见①的说明)。

            ④类对象的 const和 volatile 属性会传递给它的成员。这会影响E1.E2整个表达式结果的类型。

        (3)字符串字面值常量是个const的左值(可以取地址),采用规则③推导。而非字符串字面值常量则是个右值,采用规则④推导。

        (4)对于类型为T的左值表达式decltype总是得出T&类型除该表达式仅有一个名字外,如标记符表达式)。注意decltype(x)和decltype((x))是不同的,两者的差别仅仅在于后者加了个小括号,但前者x是个标记符表达式,后者(x)是个左值表达式。

        (5)decltype推导出的表达式,冗余的引用符(&)和CV修饰符会被忽略

    【编程实验】 decltype的推导规则

    #include <iostream>
    #include <boost/type_index.hpp>
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    //辅助类模板,用于打印T的类型
    template <typename T>
    void printType(string s)
    {
        cout << s << " = " << type_id_with_cvr<T>().pretty_name() << endl;
    }
    
    class  Foo
    {
    public:
        Foo(){}
        Foo(const Foo&){}
        int x = 0;
        static const int Number = 0;
        const int& rx = x;
        int memberfunc(int) { return 0; }
        static int staticfunc(int x, double y)
        {
            return 0;
        }
    
        void test()
        {
            decltype(this) t1;         //t1 = Foo*,因为this是个右值,返回T类型,即Foo*
            decltype(*this) t2 = *this; //t2 = Foo&。因为this是个左值,返回T&,即Foo&
    
            printType<decltype(t1)>("test(): t1");
            printType<decltype(t2)>("test(): t2");
    
        }
    
        void cfunc() const
        {
            decltype(this) t1;         //t1 = const Foo*
            decltype(*this) t2 = *this; //t2 =const Foo&。
            printType<decltype(t1)>("test(): t1");
            printType<decltype(t2)>("test(): t2");
        }
    };
    
    Foo funcTest() 
    {
        return Foo();
    }
    
    void Overloaded(int) {}
    void Overloaded(int, char) {} //重载函数
    
    int& func_l(void); //左值(lvalue)
    const int&& func_x(void); //x值(xvalue)
    const Foo func_r(void);   //纯右值(prvalue)
    const int func_ri(void);  //纯右值(prvalue)
    
    int main()
    {
        int i = 0;
        int arr[5] = { 0 };
        int* ptr = arr;
        const volatile Foo foo; //注意foo带有cv属性
    
        cout << "------------------------------实验1:expr为标识符表达式或类成员访问表达式--------------------------" << endl;
        //实验1:expr为标识符表达式或类成员访问表达式。(规则1)
        //1.1以下expr均为标识符表达式
        printType<decltype(arr)>("arr");        //arr = int[5];
        printType<decltype(ptr)>("ptr");        //ptr = int*;
        printType<decltype(ptr)*>("ptr2");      //ptr2 = int**
        printType<decltype(Foo::x)>("Foo::x");  //Foo::x = int, 可理解为Foo作用域中的x
        printType<decltype(Foo::Number)>("Foo::Number");  //Foo::Number = const int
        printType<decltype(Foo::rx)>("Foo::rx");  //Foo::rx = const int&
        //printType<decltype(Foo::memberfunc)>("Foo::memberfunc");  //非静态成员函数,编译失败!
        printType<decltype(Foo::staticfunc)>("Foo::staticfunc");  //Foo::staticfunc = staticfunc = int(int,double)
        
        //1.2 类成员访问表达式
        //decltype(foo.x)& x = foo.rx;   //编译失败,因为x为int&。由于rx是个引用类型(T&:const int&),所以表达式foo.rx求值结果为
                                         //T类型(即const int),表示rx所指的对象。两者类型不匹配
                                         //(注意foo对象的volatile会传递给rx本身,不会传递给rx所指对象)。
        decltype(foo.rx) rx = foo.rx;    //foo.rx实体为const int&类型,表达式foo.rx求值结果为const int,可以绑定到const int&
        printType<decltype(rx)>("foo.rx"); //rx实体类型为const int&
    
        //decltype(foo.x)& rx2 = foo.x; //rx2为int&类型,而右侧的表达式foo.x求值结果为const volatile int&类型(cv传递性),无法绑定到int&
    
        printType<decltype(foo.staticfunc)>("foo.staticfunc"); //foo.static = int(int, double)
    
        //1.3 重载函数
        //decltype(Overloaded) ov; //重载函数,无法编译通过!
        cout << "-------------------------------------------实验2:函数调用-------------------------------------------" << endl;
        //实验2:函数调用(直接使用函数名称时,按规则1推导;函数调用格式(但不实际调用函数),用于判断返回值的类型。)
        decltype(func_l) funcl;       //函数名:func1 = int&(void),按规则1推导(func1表示一种可调用对象。但不是函数指针或引用)
                                      //decltype(func_1)*、decltype(func_l)&才是函数指针和引用。
        decltype(func_l()) funcl2 = i;//函数调用:funcl2 = int& ,即返回值类型。
        decltype(func_x) funcx;       //函数名:funcx = const int&&(void),按规则2推导
        decltype(func_x()) funcx2 = 1;//函数调用:funcx2 = const int& ,即返回值类型。
        decltype(func_ri) funcri;     //函数名:  funcri = const int(void)。
        decltype(func_ri()) funcri2;  //函数调用:funcr4 = int,即返回值类型 (非类类型,cv舍弃)。
        decltype(func_r) funcr;       //函数名:funcr = const Foo(void),按规则4推导
        decltype(func_r()) funcr2;    //函数调用:funcr2 = const Foo ,即返回值类型 (类类型,保留cv)
    
        printType<decltype(funcl)>("func_l");
        printType<decltype(funcl2)>("func_l()");
        printType<decltype(funcx)>("func_x");
        printType<decltype(funcx2)>("func_x()");
        printType<decltype(funcri)>("func_ri");
        printType<decltype(funcri2)>("func_ri()");
        printType<decltype(funcr)>("func_r");
        printType<decltype(funcr2)>("func_r()");
    
        cout << "-----------------------------------------实验3:带括号表达式和其它------------------------------------" << endl;
        //实验3:带括号表达式和其它
        //3.1 带括号表达式
        decltype(foo.x) foox;             //foox = int,按规则1推导
        decltype((foo.x)) foox2 = foo.x;  //foox2 = const volatile int&,按规则3推导。decltype(左值表达式) -->返回左值引用
                                          //所以(foo.x)是个表达式,该括号只是说明了有更高的运算优先级,该表达式可以作为左值,返回左值引用(规则3)
    
        decltype(funcTest().rx) funct1 = i;    //funct1 = const int&,类访问表达式(规则1)
        decltype((funcTest().rx)) funct2 = 10; //funct2 = const int&,由于rx是个引用,按规则3。(见注意事项第2点)
        decltype((funcTest().x)) funct3 = 10;  //funct3 = int&&,funcTest()是个将亡值(xvalue) ,按规则2推导。
    
        printType<decltype(foox)>("foo.x");
        printType<decltype(foox2)>("(foo.x)");
        printType<decltype(funct1)>("funcTest().rx");
        printType<decltype(funct2)>("(funcTest().rx)");
        printType<decltype(funct3)>("(funcTest().x)");
        
        //3.2 左/右值表达式
        decltype(foo.Number) num = foo.Number;     //由于Number为静态成员,此处虽为类成员访问表达式,但仍按规则3推导。
                                                   //因此,此处foo.Number求值结果为const int类型的左值;decltype结果为const int&
        
        decltype(++i) i1 = i; //i1: int&, ++i返回i的左值(规则3)
        decltype(i++) i2;     //i2: int, i++返回纯右值(不能对表达式取地址)(规则4)
    
        decltype(arr[3]) arr3 = i; //arr3 = int&,操作符[]返回左值
        decltype(*ptr)   ptr2 = i; //ptr2 = int&,由于*ptr表示指针所指向的对象,是个左值(如*ptr = 4成立),所以返回引用(规则3)。
      
        decltype("SantaClaus") str = "SantaClaus"; //str = const char(&)[11],字符串字面量是左值表达式。(规则3)
        decltype(10) ten = 10;    //ten = int,非字符串字面面(规则4)
    
        decltype(std::move(i)) i3 = 10; //i3 = int&&, std::move返回int&&。规则2。
    
        int n = 0, m = 0;
        decltype(n + m) c = 0;  //c = int, n+m为右值(规则4)
        decltype(n += m) d = c; //d = int&, n +=m,返回n,是个左值(规则3)
    
        printType<decltype(num)>("foo.Number");
        printType<decltype(i1)>("i1");
        printType<decltype(i2)>("i2");
        printType<decltype(arr3)>("arr3");
        printType<decltype(ptr2)>("ptr2");
        printType<decltype(str)>("str");
        printType<decltype(ten)>("ten");
        printType<decltype(i3)>("i3");
        printType<decltype(c)>("c");
        printType<decltype(d)>("d");
    
        Foo fObj;
        fObj.test();   //t1: Foo*, t2: Foo&
        fObj.cfunc();  //t1. const Foo*, t2: const Foo& 
    
        //3.3 内置成员指针访问运算符.*和->*(注意,使用规则3推导)
        printType<decltype(foo.* & Foo::x)>("foo.* & Foo::x"); //foo.*&Foo:x : const volatile int&(规则3,cv传递)
        //printType<decltype(foo.* & Foo::rx)>("foo.* & Foo::rx"); //指向引用的指针是非法的!
    
        //3.4 指向成员变量和成员函数的指针(规则1):取地址为T*类型的纯右值(按规则4推导
        printType<decltype(&Foo::x)>("&Foo::x");         // int A::*
        printType<decltype(&fObj.x)>("&fObj.x");         // int*
        printType<decltype(&foo.x)>("&foo.x");           // const volatile int*
        //decltype(&Foo::rx);                            // 错误:指针不允许指向引用型的成员变量。
        printType<decltype(&Foo::test)>("&Foo::test");   // void (Foo::*) ()
        printType<decltype(&Foo::cfunc)>("&Foo::cfunc"); // void (Foo::*) () const
    
        cout << "-----------------------------------------实验4:引用符(&)、CV的冗余和继承--------------------" << endl;
        int& ri = i;
        const int j = 10;
        decltype(ri)& ri2 = i;    //ri2 = int&, 引用符冗余
        const decltype(j) rj = j; //rj = const int, const冗余。
        printType<decltype(ri2)>("ri2");
        printType<decltype(rj)>("rj");
    
        //cv的继承
        decltype(foo.x) foox3;             //规则1:foox3 = int,E1.E2,即E2声明的类型,foo的cv不会被按继承。
        decltype((foo.x)) foox4 = foo.x;   //规则3:foox4 = int const volatile &, foo的cv属性会被继承。
        printType<decltype(foox3)>("foox3");
        printType<decltype(foox4)>("foox4");
    
        //decltype(ptr)* ptr2 = &i; //ptr2为int**,与&i类型不匹配。
        decltype(ptr)* ptr3 = &ptr; //ptr3为int**
        auto* ptr4 = ptr;           //ptr4为int*, 注意与ptr3的区别。
        printType<decltype(ptr3)>("ptr3 ");
        printType<decltype(ptr4)>("ptr4 ");
    
        return 0;
    }
    /*输出结果:
    ------------------------------实验1:expr为标识符表达式或类成员访问表达式--------------------------
    arr = int [5]
    ptr = int *
    ptr2 = int * *
    Foo::x = int
    Foo::Number = int const
    Foo::rx = int const &
    Foo::staticfunc = int __cdecl(int,double)
    foo.rx = int const &
    foo.staticfunc = int __cdecl(int,double)
    -------------------------------------------实验2:函数调用-------------------------------------------
    func_l = int & __cdecl(void)
    func_l() = int &
    func_x = int const && __cdecl(void)
    func_x() = int const &&
    func_ri = int const __cdecl(void)
    func_ri() = int
    func_r = class Foo const __cdecl(void)
    func_r() = class Foo const
    -----------------------------------------实验3:带括号表达式和其它------------------------------------
    foo.x = int
    (foo.x) = int const volatile &
    funcTest().rx = int const &
    (funcTest().rx) = int const &
    (funcTest().x) = int &&
    foo.Number = int const &
    i1 = int &
    i2 = int
    arr3 = int &
    ptr2 = int &
    str = char const (&)[11]
    ten = int
    i3 = int &&
    c = int
    d = int &
    test(): t1 = class Foo *
    test(): t2 = class Foo &
    test(): t1 = class Foo const *
    test(): t2 = class Foo const &
    foo.* & Foo::x = int const volatile &
    &Foo::x = int Foo::*
    &fObj.x = int *
    &foo.x = int const volatile *
    &Foo::test = void (__thiscall Foo::*)(void)
    &Foo::cfunc = void (__thiscall Foo::*)(void)const
    -----------------------------------------实验4:引用符(&)、CV的冗余和继承--------------------
    ri2 = int &
    rj = int const
    foox3 = int
    foox4 = int const volatile &
    ptr3  = int * *
    ptr4  = int *
    */

    二、decltype的应用

    (一)deltype(auto):是C++14新增的类型指示符,可以用来声明变量以及推导函数返回类型。与auto与一样,它会从其初始化表达式或返回值表达式出发,采用decltype规则来推导类型

        ①用于声明变量时:该变量必须立即初始化。假设初始化表达式为e,那么变量类型则为decltype(e),也就是说在推导变量类型时,先用初始化表达式替换decltype(auto)中的auto再根据decltype的推导规则来确定变量的类型

        ②用于推导函数返回值类型时:假设函数返回表达式e,那么返回值类型为decltype(e)。也就是在推导函数返回值类型时,先用返回值表达式替换decltype(auto)中的auto然后再根据decltype的推导规则来确定返回值类型

    (二)主要应用

        ①应付不同平台下可能变化的数据类型

        ②萃取变量/表达式类型

        ③auto结合decltype构成返回类型后置语法

    【编程实验】decltype的应用

    #include <iostream>
    #include <vector>
    #include <boost/type_index.hpp>
    using namespace std;
    using boost::typeindex::type_id_with_cvr;
    
    //辅助类模板,用于打印T的类型
    template <typename T>
    void printType(string s)
    {
        cout << s << " = " << type_id_with_cvr<T>().pretty_name() << endl;
    }
    
    class Widget{};
    
    //用auto推导返回值类型
    template<typename Container, typename Index>  //Container为容器类(如vector)
    //auto authAndAccess(Container&& c, Index i) ->decltype(c[i]) //C++11,返回值类型后置语法(trailing return type syntax))
    auto authAndAccess1(Container&& c, Index i)  //C++14写法,可以省略书写返回值类型后置语法
    {
        //return std::forward<Container>(c)[i]; //完美版本:调用左值或右值版本的operator[]
        return c[i]; //由于返回值采用auto推导规则。注意,此处返回值是auto,而不是auto&),即按值推导。
                     //尽管T类型的容器类(一般operator[]都返回T&),但由于按值推导,返回值是c[i]的副本,而不是引用!
    }
    
    template<typename Container, typename Index>  //Container为容器类(如vector)
    decltype(auto) authAndAccess2(Container&& c, Index i)  //C++14
    {
        //return std::forward<Container>(c)[i]; //完美版本:调用左值或右值版本的operator[]
        return c[i];//由于返回值采用decltype推导规则。所以当operator[]返回T&,根据decltype推导规则3
                    //T&是个左值,所以返回类型也为T&,保持了其引用特性。
    }
     
    //decltype用于适用可变类型
    template<typename T>
    class MyContainer1   //不完美版本!
    {
    public:
        typename T::iterator iter; //迭代器类型,(缺点:iter类型被写死,不能应用于常量容器(如const vector<int>)。
                                   //因为常量容器必须使用常量的迭代器,即T::const_iterator类型;
                                   //C++98时,一般需MyContainer1<const T>进行偏特性来解决.
    
        void getBegin(T& c)
        {
            //...
            iter = c.begin();
        }
    };
    
    template<typename T>
    class MyContainer2   //完美版本!
    {
    public:
        decltype(T().begin()) iter; //利用T()产生临时对象,再调用其begin()获得迭代器类型,可能是itertor或const_iterator(适用性更强!)
                                    //如果T是const类型,则iter = T::const_iterator,否则为T::iterator
    
        void getBegin(T& c)
        {
            //...
            iter = c.begin();
        }
    };
    int main()
    {
        //1. 应付可变类型(主要用于模板编程中)
        using vecInt = std::vector<int>;       //普通容器
        vecInt myArray1 = { 1, 2, 3, 4, 5 };
        MyContainer1<vecInt> ct1;
        ct1.getBegin(myArray1);
    
        using veccInt = const std::vector<int>; //常量容器
        veccInt myArray2 = { 1, 2, 3, 4, 5 }; 
        MyContainer1<veccInt> ct2;
        //ct2.getBegin(myArray2); //试图调用const_iterator,但由于未定义,编译失败。
    
        //使用decltype版本的容器
        MyContainer2<vecInt> ct3;
        ct3.getBegin(myArray1);   //iter = vector<int>::iterator
    
        MyContainer2<veccInt> ct4;
        ct4.getBegin(myArray2);    //iter = vector<int>::const_iterator
    
        //2. 萃取变量类型
        vector<int> vec = { 1,2,3,4 };
        decltype(vec)::size_type st = vec.size(); ////等价于vector<int>::size_type mysize = v.size();
        
        auto lam = [](int x, int y) {return x + y; };
        printType<decltype(lam)>("lam");
    
        //3. decltype(auto): C++14新增的类型指示符
        int x = 1;
        const int& y = x;
        auto z = y;               //z: int。 (按值推导,z为副本)
        decltype(auto) z2 = y;    //z2: const int&。(按decltype规则来推导初始化表达式),相当于decltype(y)
    
        Widget w;
        const Widget& cw = w;
        auto widget1 = cw;       //widet1: Widget(按值推导)
        decltype(auto) widget2 = cw;//widget2: const Widget&,相当于decltype(cw)
    
        printType<decltype(z)>("z");
        printType<decltype(z2)>("z2");
        printType<decltype(widget1)>("widget1");
        printType<decltype(widget2)>("widget2");
    
        //auto、decltype(auto)用于返回值类型推导的区别
        //authAndAccess1(vec, 2) = 30;//编译失败。auto按值推导返回值:是个右值,不能给右值赋值!
        authAndAccess2(vec, 2) = 40;  //decltype推导返回值:是个左值。
        cout << "vec[2]=" << vec[2] << endl;
    
        return 0;
    }
    /*输出结果:
    lam = class <lambda_644304a0c6d77e252f986745e6bf364e>
    z = int
    z2 = int const &
    widget1 = class Widget
    widget2 = class Widget const &
    vec[2]=40
    */
  • 相关阅读:
    POJ 1691 Painting a Board(状态压缩DP)
    POJ 1946 Cow Cycling(抽象背包, 多阶段DP)
    Leetcode: Best Time to Buy and Sell Stock I, II
    Leetcode: Valid Palindrome
    POJ 1946 Cow Cycling
    POJ 1661 Help Jimmy(递推DP)
    POJ 1160 Post Office(区间DP)
    POJ 2486 apple tree
    Android播放器框架分析之AwesomePlaye
    【开源框架】Android之史上最全最简单最有用的第三方开源库收集整理,有助于快速开发,欢迎各位...
  • 原文地址:https://www.cnblogs.com/5iedu/p/11222078.html
Copyright © 2020-2023  润新知