c++11-17 模板核心知識(九)—— 理解decltype與decltype(auto)

  • 2020 年 11 月 28 日
  • 筆記

與模板參數推導和auto推導一樣,decltype的結果大多數情況下是正常的,但是也有少部分情況是反直覺的。

decltype介紹

給定一個name或者expression,decltype會告訴你它的類型。

我們先從正常情況開始:

const int i = 0;            // decltype(i) is const int
bool f(const Widget& w);    // decltype(w) is const Widget&
                            // decltype(f) is bool(const Widget&)

struct Point {
  int x, y;         // decltype(Point::x) is int
};          // decltype(Point::y) is int

Widget w;        // decltype(w) is Widget
if (f(w)) …       // decltype(f(w)) is bool

template<typename T>    // simplified version of std::vector
class vector {
public:
…
T& operator[](std::size_t index);
…
};

vector<int> v;      // decltype(v) is vector<int>
…
if (v[0] == 0) …      // decltype(v[0]) is int&

很直觀,沒有例外情況。 注意:decltype與auto不同,不會消除const和引用。

為什麼需要decltype

比如我們需要聲明一個函數模板,函數的返回值類型依賴函數參數的類型。在C++11中,常見的例子是返回一個container對應索引的值:

template <typename Container, typename Index> // works, but requires refinement
auto authAndAccess(Container &c, Index i) -> decltype(c[i]) {
  return c[i];
}

注意:這裡的auto跟類型推導沒有任何關係,它只是表明了這裡使用了C++11的trailing return type.

decltype(auto)

在C++11中只允許單語句的lambda表達式被推導,在C++14中之中行為被拓展到所有lambda和所有函數,包括多語句。在C++14中,上述程式碼我們可以簡寫為:

template<typename Container, typename Index>        // C++14;  not quite correct
auto authAndAccess(Container& c, Index i) { 
  return c[i];         // return type deduced from c[i]
}

注意:這裡的auto就跟類型推導有關係了。 在前面講auto推導規則的文章中提到過,auto作用在函數返回值時,使用的是模板參數推導規則,這裡就會出現問題:operator []我們希望它返回引用,但是使用auto使用模板參數推導規則時,引用會被忽略,所以下面的程式碼會報錯:

template <typename Container, typename Index>
auto authAndAccess(Container &c, Index i) {
  return c[i];
}

std::vector<int> v{1,2,3,4,5};
authAndAccess(v,2) = 10;      // error: expression is not assignable

但是使用auto -> decltype()則不會報錯,因為這裡auto不代表參數參數推導:

template <typename Container, typename Index>
auto authAndAccess(Container &c, Index i) -> decltype(c[i]) {
  return c[i];
}

std::vector<int> v{1,2,3,4,5};
authAndAccess(v,2) = 10;

所以,要想讓authAndAccess在使用auto的情況下返回引用,在C++14中,我們可以使用decltype(auto):

template <typename Container, typename Index>
decltype(auto) authAndAccess(Container &c, Index i) {
  return c[i];
}


std::vector<int> v{1,2,3,4,5};
authAndAccess(v,2) = 10;

decltype(auto)中的auto代表返回值需要被自動推導,decltype代表使用decltype來推導返回值類型。

decltype(auto)不僅可以聲明函數返回值,還可以聲明變數:

Widget w;

const Widget& cw = w;       // auto type deduction : myWidget1's type is Widget

decltype(auto) myWidget2 = cw;       // decltype type deduction : myWidget2's type is const Widget& 

注意(entity)

decltype的規則可以看官網:decltype specifier,概況下可以分為兩大類:

  • decltype ( entity ) : 如果entity是一個不被括弧包圍的標識符、類訪問表達式,那麼decltype ( entity )與entity類型一致。
  • decltype ( expression ) : 如果expression是一個表達式,計算結果為類型T,那麼:
    • 如果expression為xvalue,那麼decltype的結果是T&&.
    • 如果expression為lvalue,那麼decltype的結果是T&.
    • 如果expression為prvalue,那麼decltype的結果是T.

注意第一點中強調了entity是一個不被括弧包圍的標識符。因為當一個標識符被括弧包圍時,它就是一個左值表達式了,對應上面第二大點的第二小點。比如說int x = 0;,x是一個標識符,所以decltype(x)的結果為int。但是(x)就是一個左值表達式,decltype((x))的結果就是int&。所以下面的用法是不同的:
decltype(auto) f1() {
int x = 0;

return x; // decltype(x) is int, so f1 returns int
}

decltype(auto) f2() {
int x = 0;

return (x); // decltype((x)) is int&, so f2 returns int&
}

官網的例子能很好的概況decltype最常見的用法:

#include <iostream>
 
struct A { double x; };
const A* a;
 
decltype(a->x) y;       // type of y is double (declared type)
decltype((a->x)) z = y; // type of z is const double& (lvalue expression)
 
template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) // return type depends on template parameters
                                      // return type can be deduced since C++14
{
    return t + u;
}
 
int main() 
{
    int i = 33;
    decltype(i) j = i * 2;
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
 
    auto f = [](int a, int b) -> int
    {
        return a * b;
    };
 
    decltype(f) g = f; // the type of a lambda function is unique and unnamed
    i = f(2, 2);
    j = g(3, 3);
 
    std::cout << "i = " << i << ", "
              << "j = " << j << '\n';
}

(完)

朋友們可以關注下我的公眾號,獲得最及時的更新: