1. 程式人生 > >第2課 類型推導(2)_decltype關鍵字

第2課 類型推導(2)_decltype關鍵字

bar using 獲取 臨時 pub 通過 lsp amp 判斷

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;
}

第2課 類型推導(2)_decltype關鍵字