C++關鍵字decltype

thammer發表於2024-06-28

decltype引入的原因之一是在函式模板中遇到如下情形:

template <typename T1, typename T2>
void foo(T1 a, T2 b)
{
    ?type? tmp = a + b;
}

此時,tmp型別該定義為哪種呢?我們知道基礎資料型別相加時會自動進行型別提升,直接用T1或者T2都是不合適的。此時就需要使用新的關鍵字decltype

template <typename T1, typename T2>
void foo(T1 a, T2 b)
{
    decltype(a + b) tmp = a + b;
}

由編譯器自己在編譯過程中推導,當然也可以用auto代替。decltype關鍵字的作用是獲取提供給它的引數的資料型別。可以是變數,表示式,函式呼叫等。使用decltype的格式如下:

decltype(expression) var;

確定decltype推匯出來的型別按如下步驟:

  • 第一步:如果expression是一個沒有額外括號擴起來的變數,則var的型別和該變數型別相同,包括cv限定符;

    int x;
    int y;
    int z[10];
    int &rx = x;
    const int *pi;
    
    decltype(x) a;      //int型別
    decltype(rx) b = y; //int &型別
    decltype(pi) c;     //const int* 型別
    decltype(z) d;      //int[10]型別
    
    //除了一般變數,也可以是函式指標(起始也是一般變數,就是指標而已)
    int foo(){return 0;}
    decltype(foo) myFoo;  //int(*)()型別,注意這裡表示式是函式名
    
  • 第二步:如果expression是函式呼叫,則var的型別和函式返回值一樣;

    int foo(){return 0;}
    decltype(foo()) fooRet;	 //int型別
    

    處理函式呼叫時,它的原理是直接檢視函式宣告中的返回值型別,所以甚至都不需要看到函式實現:

    int foo();
    int main()
    {
        decltype(foo()) var;	//int型別
        return 0;
    }
    
  • 第三步:如果expression是用額外括號括起來的左值,那麼var的型別就是對應型別的引用,包括cv限定符;

    int x;
    int y;
    const int a = 1;
    const int b = 2;
    decltype((x)) ry = y;	//int &型別,如果不做賦值操作,會提示錯誤
    decltype((a)) rb = b;	//const int &型別,如果不做賦值操作,會提示錯誤
    rb = 1;	//報錯,因為rb是const int &型別
    
  • 第四步:如果expression是表示式,則var型別與其型別一樣:

    int x = 1;
    int &y = x;
    decltype(x + 1) a;     //int型別
    decltype(x + y) b;     //int型別
    decltype(3) c;         //int型別
    decltype(4L) d;        //long型別
    decltype(5.0f) e;      //float型別
    
    int foo();
    decltype(foo() + 3) f; //int型別
    

    有一個比較特殊的型別void型別,我們知道void型別用在函式宣告上,主要給函式返回值或者引數用。所以有以下變態寫法:

    void foo();
    
    decltype(foo()) bar(decltype(foo()))
    {
    }
    

    不過這種用法好像毫無意義。

還有一種情況,如下:

template <typename T1, typename T2>
?type? Add(T1 a, T2 b)
{
    return a + b;
}

如果定義了一個模板函式,將兩個不同型別的變數相加,並且返回他們的和。那此時返回值型別該是那種?或許我可以加入第三個型別解決:

template <typename T0, typename T1, typename T2>
T0 Add(T1 a, T2 b)
{
    return a + b;
}

如果呼叫如下:

char a = 1;
short b = 2;
int sum;
sum = Add<int, char, short>(a, b);
sum = Add(a, b);	//不支援型別推導,提示無法推導型別T0

好像也沒啥問題,但是需要我們知道不同型別相加時型別提升的規則,這無疑是很麻煩的。於是decltype被引入了,它結合auto關鍵字使用:

template <typename T1, typename T2>
auto Add(T1 a, T2 b) -> decltype(a + b)
{
    return a + b;
}
auto function(args...) -> type

這種函式宣告時,以auto作為返回值型別,真實型別放後面透過->連線的語法稱之為後置返回型別。其中auto表示佔位符,表示後置返回型別提供的型別,這是C++11給auto新增的一種作用。這種用法也可以用於一般函式的定義。

auto foo() -> int
{
}