第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 = ;
static const int Number = ;
const int& rx = x;
int memberfunc(int) { return ; }
static int staticfunc(int x, double y)
{
return ;
} 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 = ;
int arr[] = { };
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 = ;//函数调用: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 = ; //funct2 = const int&,由于rx是个引用,按规则3。(见注意事项第2点)
decltype((funcTest().x)) funct3 = ; //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[]) arr3 = i; //arr3 = int&,操作符[]返回左值
decltype(*ptr) ptr2 = i; //ptr2 = int&,由于*ptr表示指针所指向的对象,是个左值(如*ptr = 4成立),所以返回引用(规则3)。 decltype("SantaClaus") str = "SantaClaus"; //str = const char(&)[11],字符串字面量是左值表达式。(规则3)
decltype() ten = ; //ten = int,非字符串字面面(规则4) decltype(std::move(i)) i3 = ; //i3 = int&&, std::move返回int&&。规则2。 int n = , m = ;
decltype(n + m) c = ; //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 = ;
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 ;
}
/*输出结果:
------------------------------实验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 = { , , , , };
MyContainer1<vecInt> ct1;
ct1.getBegin(myArray1); using veccInt = const std::vector<int>; //常量容器
veccInt myArray2 = { , , , , };
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 = { ,,, };
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 = ;
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, ) = ; //decltype推导返回值:是个左值。
cout << "vec[2]=" << vec[] << endl; return ;
}
/*输出结果:
lam = class <lambda_644304a0c6d77e252f986745e6bf364e>
z = int
z2 = int const &
widget1 = class Widget
widget2 = class Widget const &
vec[2]=40
*/
第4课 decltype类型推导的更多相关文章
- 第3课 auto类型推导(2)
第3课 auto类型推导(2) 一.使用auto的优势 (一)避免使用未初始化变量 (二)可简化变量/对象类型的声明 (三) 在某些场合无法判断出类型时,可用auto自动推导(如lambda表达式) ...
- 第2课 auto类型推导(1)
第2课 auto类型推导(1) 一.auto类型推导 (一)与模板类型推导映射关系 1.auto类型推导与模板类型推导可以建立一一映射关系,它们之间存在双向的算法变换.auto扮演模板中T的角色,而变 ...
- c++11——auto,decltype类型推导
c++11中引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能够方便的获取复杂的类型,而且还能简化书写,提高编码效率. auto和decltype的类型推导都是编译器在 ...
- Effective Modern C++:01类型推导
C++的官方钦定版本,都是以ISO标准被接受的年份命名,分别是C++98,C++03,C++11,C++14,C++17,C++20等.C++11及其后续版本统称为Modern C++. C++11之 ...
- 第2课 类型推导(2)_decltype关键字
1. decltype关键字 (1)auto所修饰的变量必须被初始化,编译器才能通过初始化来确定auto所代表的类型,即必须先定义变量. (2)decltype可以在编译期推导出一个变量或表达式的结果 ...
- C++11 类型推导decltype
我们之前使用的typeid运算符来查询一个变量的类型,这种类型查询在运行时进行.RTTI机制为每一个类型产生一个type_info类型的数据,而typeid查询返回的变量相应type_info数据,通 ...
- 第1课 类型推导(1)_auto关键字
1. auto关键字 (1)auto的作用是让编译器自动推断变量的类型,而不需要显式指定类型.这种隐式类型的推导发生在编译期. (2)auto并不能代表实际的类型声明,只是一个类型声明的“占位符” ...
- C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则
背景: 最近在学习C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函数,不想由此引发了一系列的“探索”,于是就有了现在这篇博文. 前言: ...
- c++相关的类型推导
c++11和boost库增加许多关于类型推导(编译期)的关键字和类型, 用好这些机制, 对于编写项目的一些组件帮助颇大.正所谓工欲善其事,必先利其器. 1.初始化某种类型的变量 auto var = ...
随机推荐
- MySQL job/定时任务/event 学习
参考文章: https://blog.csdn.net/qq_21108311/article/details/82589850 https://blog.csdn.net/qq_27238185/a ...
- table中td文字超出长度用省略号隐藏超出内容,鼠标点击内容全部显示
1,设置css样式 <style>table {width: 100%;float: left;table-layout:fixed;width:600px;border:1px soli ...
- 【C#常用方法】2.DataTable(或DataSet)与Excel文件之间的导出与导入(使用NPOI)
DataTable与Excel之间的互导 1.项目添加NPOI的引用 NPOI项目简介: NPOI是一个开源的C#读写Excel.WORD等微软OLE2组件文档的项目,特点是可以在没有安装Office ...
- 网络编程——TCP协议、UDP协议、socket套接字、粘包问题以及解决方法
网络编程--TCP协议.UDP协议.socket套接字.粘包问题以及解决方法 TCP协议(流式协议) 当应用程序想通过TCP协议实现远程通信时,彼此之间必须先建立双向通信通道,基于该双向通道实现数 ...
- 华为手机usb调试打开后自动关闭怎么办?华为手机 usb调试为什么自动关闭?usb调试老是自动关闭怎么回事?
01 解决方法一依次点击“设置”——“系统”——“开发人员选项”先开启“开发者选项”开关. 02 然后在开启“USB调试”开关后,一并将“'仅充电'模式下允许ADB调试”选项开关打开.这样,华为手机u ...
- ES6 对象的拓展(三)
一.对象中的属性及方法1.属性属性简写:当对象属性名与属性值变量相同可以简写eg: let [name,age]=['nzc','18']; let obj = { name:name, age:ag ...
- iOS 上传appstore 一直卡在正在通过 App Store 进行鉴定(转)
原帖地址:https://www.jianshu.com/p/d28714f3ef74 手动操作一下执行xcode包里面的命令行操作: (1)找到前往>应用程序里面的xcode (2)显示包内容 ...
- helm搭建本地chart仓库及基本操作
这个步骤,是配合公司的竞赛. 因为公司这次的环境,我们只有namespace权限,而没有整个集群的管理, 而且,公司没有提供统一的helm chart repo, 所以只能自建. 参考URL: htt ...
- 【Hadoop】CDH、Presto配置问题
1.hive.properties配置如下 connector.name=hive-hadoop2 hive.metastore.uri=thrift://node001.XXXX.com:9083 ...
- AcWing 38. 二叉树的镜像
习题地址 https://www.acwing.com/solution/acwing/content/2922/ 题目描述输入一个二叉树,将它变换为它的镜像. 样例 输入树: / \ / \ / \ ...