第2课 类型推导(2)_decltype关键字

1. decltype关键字

(1)auto所修饰的变量必须被初始化,编译器才能通过初始化来确定auto所代表的类型,即必须先定义变量。

(2)decltype可以在编译期推导出一个变量或表达式的结果类型(但不会真正计算表达式的值),并且使用这个结果定义新的变量。

【实例分析】获取表达式的类型

//2.1.cpp

#include <iostream>
using namespace std;

int main()
{
    int x = 0;

    decltype(x) y = 1;      //y: int
    decltype(x + y) z = 0;  //z: int

    const int& i = x;
    decltype(i) j = y;   //j: const int&,保留cv和引用属性

    const decltype(z)* p = &z; //p: const int*,
                               //decltype(z)*在z的类型基础上加个*

    //const auto* p = &z;   //p: const int*,此处auto*中的*是冗余的
                            //相当于const auto p = &z;

    decltype(z)*  pi = &z; //pi: int*
    decltype(pi)* pp = &pi; //在pi类型的基础上加个*,即pp:int**

    return 0;
}

2.decltype(exp)的推导规则(依序判断)

(1)规则1:如果exp是一个简单的标记符表达式或者类成员访问表达式,则decltype(exp)的类型就是exp的类型,包含的cv修饰符也不会丢失。(注意,当exp是一个被重载的函数则编译不过)。

  ①标记符及标记符表达式:标记符——除去关键字、字面量等标记之外、由程序自定义的标记(token)都是标记符(identifier)。而由单个标识符对应的表达式就是标记符表达式

  ②如,int arr[4],中arr是一个标记符表达式,但arr[3]+0,arr[3]都不是标记符表达式。同理,int a,a是一个标记符表达式,而(a)或a+1都不是标记符表达式,其中(a)是一个左值表达式,而a+1是个右值表达式。、

(2)规则2:如果exp不是简单的标记符表达式,则必是xvalue(将亡值,右值中的一种。如std::move返回值、T&&函数返回值)、prvalue(纯右值,如非引用返回的对象、表达式产生的临时对象)、 lvaue(左值)三者之一。(注意cv修饰符可能会丢弃!)

  ①if exp==>lvalue then decltype(exp) ==>T& (假设exp的类型为T)

  ②if exp==>prvalue then decltype(exp) ==>T (重要提示:对于纯右值而言,只有类类型可以保留cv限定符,其它类型则会丢失cv限定)

  ③if exp==>xvalue then decltype(exp) ==>T&&

(3)规则3:如果exp被加上括号,则decltype不再认为exp是个简单的标识符表达式,然后按照规则2推理。

【实例分析】decltype的推导规则

2.2.cpp

#include <iostream>

using namespace std;

const int g_ci = 0;
const int& g_ri = g_ci;

const int foo(int){
    return 0;
}

const int bar(int){
    return 0;
}

class test
{
public:
    test& operator=(const test& rhs)
    {
        var1 = rhs.var1;
        return *this;
    }
    const double foo(int){return 0;}
    const double bar(int){return 0;}

    int var1 = 0;

    const double var2 = 0;
    static const int n = 0;
};

int arr[10];
int&& g_rr=0;  //g_rr: int&&(纯右值)
int* ptr = arr;

test t;

const int& foo2(int){
    static int kk;
    return kk;
}

const test testfunc(){
    //return test();
}

int main()
{
    //////////////////////////////////////////////////////////////////////////////////////
    //规则1:简单标记符表达式和类成员表达式
    decltype(g_ci) ci = 0;  //ci: const int;
    decltype(g_ri) ri = ci;  //ri: const int&;
    decltype(arr)  ar = {1,2,3,4,5}; //ar: int[5]

    decltype(foo)* pfbar = bar;  //pfbar:const int (*)(int),函数指针
    decltype(&foo) pfbar2 = bar; //pfbar2类型与pfbar相同

    decltype(test::foo) ptestbar = test::bar; //pftestbar: const double (test::*)(int);
    (t.*ptestbar)(123);//函数调用
    decltype(&test::foo) ptestbar2 = &test::bar; //pftestbar2类型与pftestbar相同
    (t.*ptestbar2)(123);//函数调用

    decltype(test::var1) tvar1 = 0; //tvar1: int
    decltype(t.var1)  tv1 = 0;   //tv1: int
    decltype(&test::var1) ptv1 =&test::var1; //ptv1: int test::*,可t.*ptv1=2方式来访问。

    decltype(test::var2) tvar2 = 0.0; //tvar2: const double
    decltype(t.var2) tv2 = 0.0;  //tv2: const double;
    decltype(&test::var2) ptv2 =&test::var2; //ptv2: const double test::*

    decltype(t.n) tn = 0; //tn: const int;

    //////////////////////////////////////////////////////////////////////////////////////
    //规则2:lvalue,xvalue,pvalue表达式规则
    //如果一个表达式不是“简单id-expression”,则e必是三者之一.
    //IF e ==> lvalue  THEN decltype(e) ==> T&
    //IF e ==> prvalue THEN decltype(e) ==> T  //重要提示:e为普通类型时,推导结果cv丢失。e为类类型时则保留cv
    //IF e ==> xvalue  THEN decltype(e) ==> T&&

    //函数调用,取返回值类型
    decltype(foo(123)) foo1 = 0; //foo1: int,函数调用,返回值为纯右值,cv被丢弃。
    foo1 = 1; //编译通过,证明foo1的const属性被丢弃

    decltype(t.foo(123)) tfoo = 0.0; //tfoo: double,返回值为pvalue,cv丢失。

    decltype(foo2(123)) lfoo = 0;    //lfoo: const int&, 返回值是左值,cv保留
    //lfoo = 2; //编译不通过,因为lfoo具有const属性。

    int i=0;
    int& j=i;
    decltype(++i) i1 = j; //i1: int&,即左值引用,因为++i结果是左值。
    decltype(i++) i2 = 0; //i2: int,即纯右值,因为i++结果是纯右值。

    decltype(++j) j1 = i; //j1: int&
    decltype(j++) j2 = 0; //j2: int

    decltype(std::move(i)) i3 = 0; //i3:int&&。因为i被move为xvalue。

    int m=0,n=0;
    decltype(m+n) mn = 0; //mn: int
    decltype(n +=m) mn2 = m;//mn2: int&。因为n+=m的结果是左值。

    decltype(arr[3]) ar3 = m; //ar3: int&,因为arr[3]可以被赋值,是个左值
    decltype(*ptr) ptri = m;  //ptri: int&,因为*ptr可以被赋值,是个左值。

    decltype("string") str = "string"; //str: const char(&)[7],因为字符串字面量为左值。

    //////////////////////////////////////////////////////////////////////////////////////
    //规则3:带括号表达式规则
    decltype((i)) i4 = j; //i4: int&。因为,i是个表达式,(i)也是个(左值)表达式。
    decltype((foo(123))) foo3 = 0; //foo3: int。因为foo函数的返回值为const int类型,加括号后表达式类型
                                   //仍为const int类型,是一个prvalue。故推导结果为int
    foo3 = 1;                      //编译通过,表示foo3丢弃了const属性
    decltype((testfunc())) tfunc;  //tfunc: const test,因为testfunc函数返回值为const test类型,是一个prvalue。
                                   //加括号后的表达式仍为prvalue,但由于是类类型,故要保留const属性。

    cout <<is_const<decltype(tfunc)>::value<<endl; //输出1,表示tfunc丢失了const属性。

    //decltype((t.var1)) tv; //tv: int&。因为t.var1是个左值。推导结果为左值引用,即int&。但该行会产生编译错误,
                             //因为声明引用的同时,必须初始化
    cout <<is_lvalue_reference<decltype((t.var1))>::value<<endl; //输出1,是一个左值引用

    return 0;
}

3.cv限制符的继承与冗余


差异


auto


decltype


const

volatile


①auto var(一般声明时)会可能舍弃cv。

②auto& var或auto* var(带引用或指针时)时,会保留cv属性


一般会保留cv限定符(注意,如果表达式为纯右值时,则可能丢失cv,见前面的推导规则


引用

int& b = a


①auto var = b //var:int,会舍弃引用

②auto& var =a //var:int&,要使auto变量成为另一个变量的引用,必须使用auto&


保留引用,如

decltype(b) c=a; //c:int&

decltype(a)& c=a;//c:int&


指针

int*p = &a


auto* var=p;//var: int*,注意看的是初始化表达式的类型,auto*中的*是冗余的,会被忽略掉(但注意会保留CV)。与auto var=p的声明结果是一样的。

auto* var=&p;//var:int**,原理同上。


decltype(p)* pp;//pp:int**,注意,与auto*不同decltype(p)*的*指的是p的类型的指针形式。

【实例分析】cv继承与冗余

//2.3.cpp

#include <iostream>
using namespace std;

const int ci = 0;
volatile int vi;

struct S
{
    int i;
};

const S a = {0};

volatile S b;
volatile S* p = &b;

int main()
{
    ///////////////////////////////////////////////////////////
    //const对象,使用decltype进行推导时,其成员不会继承const或volatile限定符
    //普通对象
    cout << is_const<decltype(ci)>::value << endl;    //1,是const对象
    cout << is_volatile<decltype(vi)>::value << endl; //1, 具有volatile属性

    //类对象
    cout << is_const<decltype(a)>::value << endl;    //1,是const对象
    cout << is_volatile<decltype(b)>::value << endl; //1, 具有volatile属性

    cout << is_const<decltype(a.i)>::value << endl;    //0,const属性不会继承给成员变量
    cout << is_volatile<decltype(p->i)>::value << endl; //0, volatile属性不会继承给成员变量    

    //cv限定符的冗余:通常情况下,如果推导出的类型己经有cv属性,则冗余的符号会被忽略。
    //注意,auto一般会丢弃cv,而decltype一般会保留cv。
    int i = 1;
    int& j = i;
    int* p = &i;
    const int k = 1;

    decltype(i)& var1 = i;  //var1: int&
    decltype(j)& var2 = i;  //var2: int&。j中多出一个&,则冗余的&被忽略
    cout << is_rvalue_reference<decltype(var2)>::value << endl;//0,var2不是右值引用,即不是int&&
    cout << is_lvalue_reference<decltype(var2)>::value << endl;//1,var2是左值引用,即int&

    //decltype(p)* var3 = &i; //var3:int**,与&i类型不匹配,编译不通过。
    decltype(p)* var3 = &pl;  //var3: int**。注意,decltype(p)*中的*不是冗余的!!!

    auto* v3 = p;  //v3: int*。注意auto*中的*是冗余的,相当于auto v3 = p;

    const decltype(k) var4 = 1; //var4: const int; 因为decltype(k)推导出来己带const,
                                //所以最前面的const是冗余的。

    return 0;
}
时间: 2024-10-24 09:08:10

第2课 类型推导(2)_decltype关键字的相关文章

第3课 类型推导(3)_追踪返回类型

1. 追踪返回类型的引入 (1)经典问题:泛型编程中的返回值类型(被迫引入了返回值类型R作为模板参数) template<typename R, typename T, typename U> R add(T t, U u) { return t + u; } int a = 1; float b = 2.0 auto c = add<decltype(a+b)>(a,b); //不能自动推导函数的返回值.为了获得返回值的类型,该函数的使用者需要知道add函数的内部实现(如:a+b

《Effective Modern C++》翻译--条款2: 理解auto自动类型推导

条款2: 理解auto自动类型推导 如果你已经读过条款1关于模板类型推导的内容,那么你几乎已经知道了关于auto类型推导的全部.至于为什么auto类型推导就是模板类型推导只有一个地方感到好奇.那是什么呢?即模板类型推导包括了模板.函数和参数,而auto类型推断不用与这些打交道. 这当然是真的,但是没关系.模板类型推导和auto自动类型推导是直接匹配的.从字面上看,就是从一个算法转换到另一个算法而已. 在条款1中,阐述模板类型推导采用的是常规的函数模板: template<typename T>

《Effective Modern C++》读书笔记 Item 2 auto的类型推导

注意: 还要学习一个 ↑↑↑↑ 这样的方框里的片段完全不来自于原书,而是我自己的理解. Item 2 Understand auto type deduction - auto类型推导 在C++11之前,auto 关键字一直是用于声明自动储存类型的变量时使用的,基本上没有什么实际作用,地位和 export 关键字(用于向编译单元之外导出模板,也在C++11中被取消)类似. 在C++11中,auto 终于不再废材,终于具备了类似C#中 var 关键字的效果,可以自动推导出变量的类型,可以少打几个字

《Effective Modern C++》翻译--条款2: 理解auto自己主动类型推导

条款2: 理解auto自己主动类型推导 假设你已经读过条款1关于模板类型推导的内容,那么你差点儿已经知道了关于auto类型推导的所有. 至于为什么auto类型推导就是模板类型推导仅仅有一个地方感到好奇.那是什么呢?即模板类型推导包含了模板.函数和參数,而auto类型判断不用与这些打交道. 这当然是真的.可是没关系. 模板类型推导和auto自己主动类型推导是直接匹配的. 从字面上看,就是从一个算法转换到还有一个算法而已. 在条款1中.阐述模板类型推导採用的是常规的函数模板: template<ty

c++11——auto,decltype类型推导

c++11中引入了auto和decltype关键字实现类型推导,通过这两个关键字不仅能够方便的获取复杂的类型,而且还能简化书写,提高编码效率.     auto和decltype的类型推导都是编译器在编译的时候完成的,auto是通过定义auto变量时候给出的表达式的值推导出实际类型,并且在声明auto变量时必须马上初始化:decltype通过表达式的值推导出实际的类型,但是可以只声明变量,而不赋值. auto类型推导 1. auto推导 auto x = 5; //被编译器推导为int类型 au

auto类型推导

引言 auto : 类型推导. 在使用c++的时候会经常使用, 就像在考虑STL时迭代器类型, 写模板的时候使用auto能少写代码, 也能帮助我们避免一些隐患的细节. auto初始化 使用auto型别推导要求必须在定义时初始化, 毕竟需要根据对象的类型推导左值对象的型别. auto j; // error. 必须初始化 auto i = 0; // i 推导型别为 int vector<int> v; auto vv = v.cbegin(); // vv 推导型别为 const int* 但

Atitit.变量的定义&#160;获取&#160;储存&#160;物理结构&#160;基本类型简化&#160;隐式转换&#160;类型推导&#160;与底层原理&#160;attilaxDSL

Atitit.变量的定义 获取 储存 物理结构 基本类型简化 隐式转换 类型推导 与底层原理 attilaxDSL 1.1. $ 美元字符, php 黑头1 1.2. 默认变量的范围和声明:1 1.3. 变量的存储,储存在变量池里Map(varName,varVal)1 1.3.1. 复合变量1 1.4. 变量类型简化: 字符串 数字,  bool1 1.5. 变量自动隐式转换2 1.6. 类型推导2 2. 参考 复合变量2 1.1. $ 美元字符, php 黑头 1.2. 默认变量的范围和声明

C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则

背景:    最近在学习C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函数,不想由此引发了一系列的“探索”,于是就有了现在这篇博文. 前言:      右值引用无疑是C++11新特性中一颗耀眼的明珠,在此基础上实现了移动语义和完美转发,三者构成了令很多C++开发者拍案叫绝的“铁三角”(当然不是所有C++开发者).而在这个“铁三角”中,有一个无法回避的关键细节,那就是引用叠加规则和模板参数类型推导规则.其实,关于这两个规则,可查到的资料不少,但都

初窥C++11:自动类型推导与类型获取

auto 话说C语言还处于K&R时代,也有auto a = 1;的写法.中文译过来叫自动变量,跟c++11的不同,C语言的auto a = 1;相当与 auto int a = 1;语句. 而C++11的auto是有着严格的类型推导出来的.以前是这么写 int a = 1; 现在,编译器知道a是int型了.所以可以这么写 auto a = 1; 对于类型比较长的,如vector<string>::iterator这类的,能少敲些字符了. 如果仅仅就这点作用,那么对编程实在没什么太大的益