第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的返回值类型