摘要訊息 : C++ 14 Proposal N3638《Return type deduction for normal functions》導讀
C++ 14 Proposal N3638《Return type deduction for normal functions》導讀
C++ 11 引入了尾置回傳型別, 但是大神們覺得這還不太夠, 他們希望 C++ 直接可以支援函式回傳型別推導. N3638 提出了這樣的需求. 由於之後的文章需要用到回傳型別推導的特性, 所以我提前發布這篇文章
不論在名稱空間中還是在類別之下, C++ 14 都支援了函式回傳型別推導, 也就是說之前我們寫的程式碼 :
emplate <typename T, typename U>
auto add(const T &x, const U &y) -> decltype(x + y);
在 C++ 14 下, 可以直接省略尾置回傳型別 :
template <typename T, typename U>
auto add(const T &x, const U &y);
我們所說的都是比較簡略的情況, 這篇 Paper 中提到了一些比較複雜的情況
當我們宣告一個函式的時候, 若回傳型別使用 auto
, 那麼在實作函式的時候回傳型別也必須為 auto
:
auto f();
auto f() {
return 0;
} //OK
int f(); //Error
當函式的回傳型別需要推導, 但是函式在呼叫的時候無法推導得到回傳型別的時候, 會產生編碼錯誤 :
auto f();
int main(int argc, char *argv[]) {
int a {f()}; //Error
}
函式樣板在特製化的時候, 若原函式樣板的回傳型別需要推導, 那麼特製化而來的函式的回傳型別也必須經過推導 :
template <typename T>
auto f();
template <>
auto f<char>(); //OK
template <>
int f<int>(); //Error
若函式存在多個 return
陳述式, 那麼每一個 return
陳述式回傳的物件其型別應該一致, 否則會產生編碼錯誤 :
auto f(int a) {
if(a >= 10) {
return 1;
}
return false; //Error
}
對於遞迴來說, 回傳型別同樣可以推導, 但是以下的遞迴無法進行推導 :
auto f() {
return f(); //Error
}
對於回傳型別為 auto &
的函式, 若函式實作中沒有回傳任何物件, 那麼將會產生編碼錯誤 :
auto &f() {} //Error
lambda 表達式也支援了回傳型別推導 :
auto lambda {[]() -> auto {
//...
}};
回傳型別推導不可以用於虛擬函式 :
class Foo {
public:
virtual auto Foo() {} //Error
};
若 return
陳述式中出現了初始化列表, 那麼不進行推導 :
auto f {
return {1, 2, 3}; //Error
};
但是若要回傳一個 std::initializer_list
, 仍然可以通過宣告一個推導為 std::initializer_list
的變數, 然後回傳這個變數來實現 :
auto f() {
auto list = {1, 2, 3};
return list; //OK
};
這就是 Paper N3922 提出修改 auto
的推導規則的原因之一. 因為 std::initializer_list
的生存週期僅限於函式 f
之內, 如果回傳它, 就相當於回傳一個陣列 :
auto f() {
int arr[] {1, 2, 3};
return arr; //使用 arr 中的任意值都會有未定行為
};
除了這些之外, Paper 中還提到了 decltype(auto)
, 這個我們之前已經說過了, 就不再重複了
實際上我並不推薦使用這個特性, 特別是針對程式庫作者而言, 因為用戶在閱讀程式碼的時候, 看到 auto
可能會被疑惑 : 這個函式到底回傳什麼? 假如某個函式有兩百行之多, 那麼我想有些人看到函式的回傳型別需要推導, 可能會直接放棄使用這個程式庫. 當然, 這個特性也不是毫無用武之地的, 文章開頭給出了一個兩個值相加的實例. 事實上, 可能會出現這種情況 :
template <typename Fx, typename Gx>
Hx add(Fx f, Gx g);
我們並不知道 Fx
和 Gx
相加會產生什麼樣的結果, 所以 Hx
可能是未知的, 需要手動給定 :
template <typename Hx, typename Fx, typename Gx>
Hx add(Fx f, Gx g);
這樣, 函式 add
在呼叫的時候需要指定第一個樣板參數. 然而, 在 C++ 11 中我們可以使用 decltype
和尾置回傳型別 :
template <typename Fx, typename Gx>
auto add(Fx f, Gx g) -> decltype(f + g);
在 C++ 14 中運用回傳型別推導這個特性, 可以直接省略 decltype
:
template <typename Fx, typename Gx>
auto add(Fx f, Gx g);
我再用另外一個實例說明. 設有一引數包 ...args
, 每一個引數對應的型別都支援比較操作, 我們需要對裡面的引數進行排序, 然後將其按順序放進 std::tuple
然後回傳. 那麼這個回傳型別就完全未知, 因為 std::tuple
中的樣板引數完全由引數包排序之後, 一定位置的引數對應的型別所決定. 這個函式可以宣告為 :
template <typename ...Args>
auto sort(Args &&...args);
若函式呼叫 sort(1, 'a', 9.9f, 0.0, U'l');
最後得到的物件應該是 : std::tuple<double, int, flost, char, char32_t>
, 其中存儲了 0.0
, 1
, 9.9f
, 'a'
和 U'l'
對於 lambda 表達式來說, 其實 C++ 11 就已經支援了回傳型別推導, 只要省略掉 lambda 表達式的尾置回傳型別即可. 在 C++ 14 中, 只不過將回傳型別使用 auto
佔位後強行要求編碼器進行推導罷了
自創文章, 原著 : Jonny. 如若閣下需要轉發, 在已經授權的情況下請註明本文出處 :