來看看另一個函式模版的例子:
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
auto addThese(T begin, T end) {
auto r = *begin;
for(auto it = begin + 1; it != end; it++) {
r += *it;
}
return r;
}
int main() {
vector<int> vt = {1, 2, 3};
cout << addThese(vt.begin(), vt.end()) << endl;
vector<string> vt2 = {"Justin", "Monica", "Irene"};
cout << addThese(vt2.begin(), vt2.end()) << endl;
return 0;
}
addThese
傳回迭代器範圍內元素的加總,就範例來說,因為 vt
是 vector<int>
,因此元素型態是 int
,現在問題來了,如果想在標頭檔宣告函式原型呢?這樣行不通:
template <typename T>
auto addThese(T begin, T end);
因為沒有程式碼上下文,是要怎麼 auto
傳回型態呢?就範例來說,*begin
的型態就是 int
,那麼這樣呢?
template <typename T>
decltype(*begin) addThese(T begin, T end);
這樣也行不通,因為編譯器剖析程式碼到 decltype(*begin)
,根本還沒看到 begin
參數,像這種情況,可以使用尾端傳回型態(tailing return type):
#include <iostream>
#include <vector>
using namespace std;
template <typename T>
auto addThese(T begin, T end) -> decltype(*begin + *end);
int main() {
vector<int> vt = {1, 2, 3};
cout << addThese(vt.begin(), vt.end()) << endl;
return 0;
}
template <typename T>
auto addThese(T begin, T end) -> decltype(*begin + *end) {
auto r = *begin;
for(auto it = begin + 1; it != end; it++) {
r += *it;
}
return r;
}
在範例中,編譯器已經看到了參數 begin
與 end
,之後使用 -> decltype(*begin + *end)
就沒問題,那為什麼不用 decltype(*begin)
呢?
因為 *begin
是個 lvalue,若迭代器中的元素型態是 E
,那 decltype(*begin)
會推斷出 E&
,這樣的話,傳回型態會參考區域變數 r
,然而函式執行完後 r
就無效了,因此不能使用 decltype(*begin)
;這邊需要的是個 rvalue,以令其推斷出 E
,因此使用 decltype(*begin + *end)
。
尾端傳回型態並不是只能用在模版,有些偏好將函式傳回型態寫在最後的開發者,會特意這麼撰寫函式:
auto add(int a, int b) -> int {
return a + b;
}
另一個會運用到的場合,可能是在遇到底下的情況時:
class Foo {
public:
class Orz {
};
Orz* orz();
};
Foo::Orz* Foo::orz() {
return new Foo::Orz();
}
之後會談到類別定義,簡單來說,這邊定義了一個巢狀類別,在實作 orz
成員函式時,必須得以 Foo::Orz*
來指明傳回型態,因為必須知道是在哪個類別中的內部類別,然而,可以使用尾端傳回型態來簡化:
class Foo {
public:
class Orz {
};
Orz* orz();
};
auto Foo::orz() -> Orz* {
return new Foo::Orz();
}
因為 auto
後的 Foo::
已經指明了外部類別了,尾端傳回型態時就可以直接指定 Orz*
,不用再加上 Foo::
。
簡單來說,尾端傳回型態既有的型態,或者取用 ->
前看過的相關名稱,用以宣告或簡化傳回型態。