浅墨浓香

想要天亮进城,就得天黑赶路。

导航

第4课 decltype类型推导

Posted on 2019-07-21 18:14  浅墨浓香  阅读(1439)  评论(0编辑  收藏  举报

第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
*/

三、declval

(一)原型

//函数模板
std::declval (c++11 only)
template<typename T>
typename add_rvalue_reference<T>::type declval() noexcept;

(二)注意事项
    1、declval是一个函数,无参,也无实际的函数体。返回值的类型为T的右值引用。注意,第2个typename用于说明add_rvalue_reference<T>::type是一个类型,而不是其它(如静态变量)。
    2、当调用declval<T>()函数后,返回一个T类型的实例(注意,由于没于实际的函数体,declval不会真正生成这个实例,而只是用于说明其返回值是一个T类型的对象。而且,即使没有默认构造函数或该类型不可以创建对象,也不影响declval的工作)
    3、decltype()用于推导类型,而declval<T>()可理解为用于创建T类型对象的实例

【实例分析】

    template<typename TFunc, typename...Args>
    decltype(std::declval<TFunc>()(std::declval<Args>()...))
    invoke(TFunc func, Args...args)
    {
        auto res = func(std::forward<Args>(args)...);
        return ret;
    }
    //分析:
    //declval<TFunc>():创建T类型对象的实例,即一个函数对象,假设为func
    //declval<Args>():创建参数Args类型的实例,即func函数的一个个实参arg1、arg2、arg3...
    //decltype(func(arg1,arg2,arg3...)用于推导函数func的返回值类型