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
實現,完成了大部分功能(實現了tie
和 get
)。
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);
}