C++ 模板簡介(三)—— Tuple

Tuple

導入

我們可以通過 std::tuple 構造一個複合類型,比如可以使得函數擁有“多個”返回值:

std::tuple<int, int> divide(int a, int b) {
    return make_tuple(a / b, a % b);
}

然後我們可以通過 get<> 函數來獲得 tuple 內存儲的值:

auto result = divide(4, 3);
int quotient = std::get<0>(result); // 獲取 tuple 的第 0 個數
int remainder = std::get<1>(result); // 獲取 tuple 的第 1 個數

還可以:

int quotient, remainder;
std::tie(quotient, remainder) = divide(4, 3);

或者在 C++17 中:

auto [ quotient, remainder ] = divide(4, 3);

是不是更有多返回值的感覺?

實現

首先我們要知道我們如何實現一個 tuple。借鑑之前實現一系列模板元編程得到的基本函數的思想,我們只令 tuple<head, tail...> 這個類型只存 head 類型的變量,剩餘類型的變量在 tuple<tail...> 中保存。那麼如何實現這樣的分開保存呢?一種方法是在 tuple 類內存兩個變量,一個是 head 類型的變量即當前值,第二個是 tuple<tail...> 類型的變量存剩餘值。這樣我們在 get 的時候就直接遞歸拿值即可(比較樸素的方法)。但是這樣的話 get 函數的性能就會下降,因爲在我們只需要特定的值的情況下, get 卻可能需要遞歸比較多層,而get函數本來要獲取的值的下標識編譯期確定的,我們卻在運行期做了遍歷 tuple 的工作,不符合我們的零開銷策略(不過加上 constexpr 之後這種方法的開銷就降爲零了)。

接下來我們介紹另一種方法實現,就是繼承,如果我們存在 tuple<Args...>,我們要找特定的值,遍歷一次其祖先,和第一種方法一樣。爲了能夠避免遍歷,我們在模板參數中添加一個 N 表示當前的部分 tuple 是原 tuple 的第幾個元素開始的,新的類我們稱作 tuple_impl。那麼 tuple_impl<N, head, tail...> 的父類就理所當然的是 tuple_impl<N + 1, tail...>,即父類是下一個元素開始的子 tuple。那麼這麼實現有什麼好處?我們在實現 get 函數的時候就可以這麼寫:

template <std::size_t N, typename _Head, typename... _Tail>
constexpr const _Head &get_helper(const tuple_impl<N, _Head, _Tail...> &t) {
    return tuple_impl<N, _Head, _Tail...>::head(t);
}

template <std::size_t N, typename... Args>
constexpr const tuple_element_t<N, tuple<Args...>> &
get(const tuple<Args...> &t) {
    return get_helper<N>(t);
}

你可以看到,get函數調用了get_helper<N>函數,tuple<Args...>類是tuple_impl<0, Args...> 的子類,t 傳入 get_helper<N> 後變成了 tuple_impl<N, Args2...>,而這個 Args2 是我們交由編譯器自動推導的(get_helper<N> 的參數我們只給了 N 而沒有給 _Head_Tail)。也就是說我們利用了繼承的機制後,我們可以直接拿到 tuple<Args...> 的第 N 個父類 tuple_impl<N, Args2...>!從而直接獲得第 N 個元素,從而避免了遞歸調用(而且由於我們沒有虛函數和虛繼承,所以這個是零開銷的)。

下面是完整的 tuple 實現,完成了大部分功能(實現了tieget)。

template <size_t N, typename... _Elements>
struct tuple_impl;

template <std::size_t N, typename _Head>
struct tuple_impl<N, _Head> {

    explicit tuple_impl(const _Head &head) : value(head) {}

    template <typename _UHead>
    void assign(const tuple_impl<N, _UHead> &in) {
        head(*this) = tuple_impl<N, _UHead>::head(in);
    }

    static constexpr _Head &head(tuple_impl &t) {
        return t.value;
    }

    static constexpr const _Head &head(const tuple_impl &t) {
        return t.value;
    }

    _Head value;
};

template <std::size_t N, typename _Head, typename... _Tail>
struct tuple_impl<N, _Head, _Tail...>
    : public tuple_impl<N + 1, _Tail...> {

    typedef tuple_impl<N + 1, _Tail...> base_type;

    explicit tuple_impl(const _Head &head, const _Tail &... tail)
        : base_type(tail...), value(head) {}
    
    template <typename... _UElements>
    void assign(const tuple_impl<N, _UElements...> &in) {
        head(*this) = tuple_impl<N, _UElements...>::head(in);
        tail(*this).assign(tuple_impl<N, _UElements...>::tail(in));
    }

    static _Head &head(tuple_impl &t) {
        return t.value;
    }

    static const _Head &head(const tuple_impl &t) {
        return t.value;
    }

    static base_type &tail(tuple_impl &t) {
        return t;
    }

    static const base_type &tail(const tuple_impl &t) {
        return t;
    }

    _Head value;
};

template <typename... _Elements>
struct tuple : public tuple_impl<0, _Elements...> {

    typedef tuple_impl<0, _Elements...> base_type;

    constexpr tuple(const _Elements &... elements)
        : base_type(elements...) {}

    tuple &operator=(const tuple &in) {
        this->assign(in);
        return *this;
    }

    template <typename... _UElements>
    tuple &operator=(const tuple<_UElements...> &in) {
        this->assign(in);
        return *this;
    }
};

template <std::size_t N, typename... _Elements>
constexpr tuple<_Elements &...> tie(_Elements &... args) {
    return tuple<_Elements &...>(args...);
}

template <std::size_t N, typename... _Elements>
struct tuple_element;

template <std::size_t N, typename _Head, typename... _Tail>
struct tuple_element<N, tuple<_Head, _Tail...>> {
    using type = typename tuple_element<N - 1, tuple<_Tail...>>::type;
};

template <typename _Head, typename... _Tail>
struct tuple_element<0, tuple<_Head, _Tail...>> {
    using type = _Head;
};

template <std::size_t N, typename... _Elements>
using tuple_element_t = typename tuple_element<N, _Elements...>::type;

template <std::size_t N, typename _Head, typename... _Tail>
constexpr _Head &get_helper(tuple_impl<N, _Head, _Tail...> &t) {
    return tuple_impl<N, _Head, _Tail...>::head(t);
}

template <std::size_t N, typename _Head, typename... _Tail>
constexpr const _Head &get_helper(const tuple_impl<N, _Head, _Tail...> &t) {
    return tuple_impl<N, _Head, _Tail...>::head(t);
}

template <std::size_t N, typename... Args>
constexpr tuple_element_t<N, tuple<Args...>> &
get(tuple<Args...> &t) {
    return get_helper<N>(t);
}

template <std::size_t N, typename... Args>
constexpr const tuple_element_t<N, tuple<Args...>> &
get(const tuple<Args...> &t) {
    return get_helper<N>(t);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章