尾端傳回型態


來看看另一個函式模版的例子:

#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 傳回迭代器範圍內元素的加總,就範例來說,因為 vtvector<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;
}

在範例中,編譯器已經看到了參數 beginend,之後使用 -> 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::

簡單來說,尾端傳回型態既有的型態,或者取用 -> 前看過的相關名稱,用以宣告或簡化傳回型態。