2012-07-04 41 views
16

我最近遇到這個難題,終於能夠找出一個黑客答案(使用索引數組),並且想分享它(下面的答案)。我確信有使用模板遞歸的答案和使用boost的答案;如果你有興趣,請分享其他方式來做到這一點。我認爲將這些全部放在一個地方可能會使其他人受益,並且可以用於學習一些很酷的C++ 11模板元編程技巧。如何在C++ 11(STL)中創建一個拉伸兩個元組的函數?

問題: 長度相等的假設兩個元:

auto tup1 = std::make_tuple(1, 'b', -10); 
auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!")); 

你如何創建一個將「壓縮」兩元組的功能成對的異類元組?

std::tuple< 
    std::pair<int, double>, 
    std::pair<char, int>, 
    std::pair<int, std::string> > result = 
    tuple_zip(tup1, tup2); 

std::get<0>(result) == std::make_pair(1, 2.5); 
std::get<1>(result) == std::make_pair('b', 2); 
std::get<2>(result) == std::make_pair(-10, std::string("even strings?!")); 
+2

現在的獎金:1 /你可以使它工作,即使是不等長的元組(例如填充'nullptr_t')?和更多的參與2 /你可以使它與任意數量的元組一起工作嗎? –

+0

* zip兩個元組* *是什麼意思?你能發佈預期的結果嗎? –

+0

@BЈовић我在下面的答案中添加了最終'main'例程的所需輸出。通過zip,我的意思是拿上面的兩個元組('tup1'和'tup2'),並在這種情況下創建一個包含3個元素的元組,其中第一個元素是'make_pair(1,2.5)'等等 – user

回答

17

首先,指數陣列的簡要概述:

template<std::size_t ...S> 
struct seq { }; 

// And now an example of how index arrays are used to print a tuple: 
template <typename ...T, std::size_t ...S> 
void print_helper(std::tuple<T...> tup, seq<S...> s) { 
    // this trick is exceptionally useful: 
    // ((std::cout << std::get<S>(tup) << " "), 0) executes the cout 
    // and returns 0. 
    // { 0... } expands (because the expression has an S in it), 
    // returning an array of length sizeof...(S) full of zeros. 
    // The array isn't used, but it's a great hack to do one operation 
    // for each std::size_t in S. 
    int garbage[] = { ((std::cout << std::get<S>(tup) << " "), 0)... }; 
    std::cout << std::endl; 
} 

現在使用我們的print_helper功能:

int main() { 
    print_helper(std::make_tuple(10, 0.66, 'h'), seq<0,1,2>()); 
    return 0; 
} 

雖然,打字seq<0,1,2>可能有點痛苦。因此,我們可以使用模板遞歸來創建一個類來生成seq S,使gens<3>::type相同seq<0,1,2>

template<std::size_t N, std::size_t ...S> 
struct gens : gens<N-1, N-1, S...> { }; 

template<std::size_t ...S> 
struct gens<0, S...> { 
    typedef seq<S...> type; 
}; 

int main() { 
    print_helper(std::make_tuple(10, 0.66, 'h'), gens<3>::type()); 
    return 0; 
} 

由於Ngens<N>::type將永遠是在元組元素的數量,你可以用print_helper,使其更容易:

template <typename ...T> 
void print(std::tuple<T...> tup) { 
    print_helper(tup, typename gens<sizeof...(T)>::type()); 
} 

int main() { 
    print(std::make_tuple(10, 0.66, 'h')); 
    return 0; 
} 

注意,模板參數可以自動推導(輸入了這一切,將是一個痛苦的人會不是嗎?)。

現在,tuple_zip功能:

和以前一樣,開始與助手功能:

template <template <typename ...> class Tup1, 
    template <typename ...> class Tup2, 
    typename ...A, typename ...B, 
    std::size_t ...S> 
auto tuple_zip_helper(Tup1<A...> t1, Tup2<B...> t2, seq<S...> s) -> 
decltype(std::make_tuple(std::make_pair(std::get<S>(t1),std::get<S>(t2))...)) { 
    return std::make_tuple(std::make_pair(std::get<S>(t1), std::get<S>(t2))...); 
} 

的代碼是有點棘手,尤其是尾部的返回類型(返回類型聲明作爲auto並且在參數被定義之後與->一起提供)。這讓我們避免甚至定義返回類型是什麼,問題簡單地宣佈它會返回在函數體中使用表達式(如果xyint S,delctype(x+y)是在編譯時解析爲int)。

現在把它包在提供使用適當的seq<0, 1...N>gens<N>::type功能:

template <template <typename ...> class Tup1, 
    template <typename ...> class Tup2, 
    typename ...A, typename ...B> 
auto tuple_zip(Tup1<A...> t1, Tup2<B...> t2) -> 
decltype(tuple_zip_helper(t1, t2, typename gens<sizeof...(A)>::type())) { 
    static_assert(sizeof...(A) == sizeof...(B), "The tuple sizes must be the same"); 
    return tuple_zip_helper(t1, t2, typename gens<sizeof...(A)>::type()); 
} 

現在你可以使用它作爲問題規定:

int main() { 
    auto tup1 = std::make_tuple(1, 'b', -10); 
    auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!")); 
    std::tuple< 
    std::pair<int, double>, 
    std::pair<char, int>, 
    std::pair<int, std::string> > x = tuple_zip(tup1, tup2); 

    // this is also equivalent: 
    // auto x = tuple_zip(tup1, tup2); 

    return 0; 
} 

最後,如果你提供一個<< operator std::pair您可以使用我們上面定義的打印功能打印壓縮結果:

template <typename A, typename B> 
std::ostream & operator << (std::ostream & os, const std::pair<A, B> & pair) { 
    os << "pair("<< pair.first << "," << pair.second << ")"; 
    return os; 
} 

int main() { 
    auto tup1 = std::make_tuple(1, 'b', -10); 
    auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!")); 
    auto x = tuple_zip(tup1, tup2); 

    std::cout << "zipping: "; 
    print(tup1); 
    std::cout << "with : "; 
    print(tup2); 

    std::cout << "yields : "; 
    print(x); 

    return 0; 
} 

的輸出是:

壓縮和解:1個B 10
用:2.5 2甚至串?
收率:對(1,2.5)對(B,2)對(10,即使字符串?)

std::arraystd::tuple是在編譯時定義,並且因此它可以被用來生成更優化的代碼(與容器相比,在編譯時已知更多信息,如std::vectorstd::list)。所以,即使有時候有些工作,你也可以使用它來製作快速且聰明的代碼。快樂黑客!


編輯:

按照要求,允許不同的尺寸,並與空指針填充的元組:

template <typename T, std::size_t N, std::size_t ...S> 
auto array_to_tuple_helper(const std::array<T, N> & arr, seq<S...> s) -> decltype(std::make_tuple(arr[S]...)) { 
    return std::make_tuple(arr[S]...); 
} 

template <typename T, std::size_t N> 
auto array_to_tuple(const std::array<T, N> & arr) -> decltype(array_to_tuple_helper(arr, typename gens<N>::type())) { 
    return array_to_tuple_helper(arr, typename gens<N>::type()); 
} 

template <std::size_t N, template <typename ...> class Tup, typename ...A> 
auto pad(Tup<A...> tup) -> decltype(tuple_cat(tup, array_to_tuple(std::array<std::nullptr_t, N>()))) { 
    return tuple_cat(tup, array_to_tuple(std::array<std::nullptr_t, N>())); 
} 

#define EXTENSION_TO_FIRST(first,second) ((first)>(second) ? (first)-(second) : 0) 

template <template <typename ...> class Tup1, template <typename ...> class Tup2, typename ...A, typename ...B> 
auto pad_first(Tup1<A...> t1, Tup2<B...> t2) -> decltype(pad<EXTENSION_TO_FIRST(sizeof...(B), sizeof...(A)), Tup1, A...>(t1)) { 
    return pad<EXTENSION_TO_FIRST(sizeof...(B), sizeof...(A)), Tup1, A...>(t1); 
} 

template <template <typename ...> class Tup1, template <typename ...> class Tup2, typename ...A, typename ...B> 
auto diff_size_tuple_zip(Tup1<A...> t1, Tup2<B...> t2) -> 
    decltype(tuple_zip(pad_first(t1, t2), pad_first(t2, t1))) { 
    return tuple_zip(pad_first(t1, t2), pad_first(t2, t1)); 
} 

而且順便說一句,你將需要這個,現在使用我們的方便的print功能:

std::ostream & operator << (std::ostream & os, std::nullptr_t) { 
    os << "null_ptr"; 
    return os; 
} 
+3

很好的整體解決方案。我只會有一些評論。 1 /我會使用'unsigned'作爲索引(至少),因爲您不期望負面索引,是嗎? 2 /''static_assert'而不是'assert'會受到歡迎。 3 /該對可能應該由'operator <<'過載中的'const&'來完成。 –

+0

+1所有好主意。將更新。 – user

+1

推廣'tuple_zip'來操作任意數量的元組作爲讀者的練習? :) –

0

對於任意數量的元組來說這並不是非常困難。

一種方法是製作一個函數,將一個特定索引處的所有元素從N個元組收集到一個新的元組中。然後有另一個函數將這些元組收集到原始元組中每個索引的新元組中。

所有這些都可以通過使用參數包擴展表達式來完成,而不需要任何遞歸函數。

#include <cstddef> 
#include <tuple> 

namespace detail { 
    // Describe the type of a tuple with element I from each input tuple. 
    // Needed to preserve the exact types from the input tuples. 
    template<std::size_t I, typename... Tuples> 
    using zip_tuple_at_index_t = std::tuple<std::tuple_element_t<I, std::decay_t<Tuples>>...>; 

    // Collect all elements at index I from all input tuples as a new tuple. 
    template<std::size_t I, typename... Tuples> 
    zip_tuple_at_index_t<I, Tuples...> zip_tuple_at_index(Tuples && ...tuples) { 
     return {std::get<I>(std::forward<Tuples>(tuples))...}; 
    } 

    // Create a tuple with the result of zip_tuple_at_index for each index. 
    // The explicit return type prevents flattening into a single tuple 
    // when sizeof...(Tuples) == 1 or sizeof...(I) == 1 . 
    template<typename... Tuples, std::size_t... I> 
    std::tuple<zip_tuple_at_index_t<I, Tuples...>...> tuple_zip_impl(Tuples && ...tuples, std::index_sequence<I...>) { 
     return {zip_tuple_at_index<I>(std::forward<Tuples>(tuples)...)...}; 
    } 

} 

// Zip a number of tuples together into a tuple of tuples. 
// Take the first tuple separately so we can easily get its size. 
template<typename Head, typename... Tail> 
auto tuple_zip(Head && head, Tail && ...tail) { 
    constexpr std::size_t size = std::tuple_size_v<std::decay_t<Head>>; 

    static_assert(
     ((std::tuple_size_v<std::decay_t<Tail>> == size) && ...), 
     "Tuple size mismatch, can not zip." 
    ); 

    return detail::tuple_zip_impl<Head, Tail...>(
     std::forward<Head>(head), 
     std::forward<Tail>(tail)..., 
     std::make_index_sequence<size>() 
    ); 
} 

看到它在這裏的行動:https://wandbox.org/permlink/EQhvLPyRfDrtjDMw

我使用了一些C++十七分之十四功能,但沒有必要的。要替換的最困難的部分將是檢查元組大小的摺疊表達式。這可能必須成爲遞歸檢查。

相關問題