我最近遇到了这个难题,终于能够找到一个黑客答案(使用索引数组),并想分享它(答案如下)。我确信有使用模板递归的答案和使用
boost
的答案;如果您有兴趣,请分享其他方法来做到这一点。我认为将这些全部放在一处可能会让其他人受益,并且对于学习一些很酷的 C++11 模板元编程技巧很有用。
问题: 给定两个长度相等的元组:
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?!"));
首先,快速概述一下索引数组:
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
,这样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;
}
由于
N
中的 gens<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
,并在定义参数后提供 ->
)。这让我们可以避免“定义”返回类型的问题,只需声明它返回函数体中使用的表达式(如果 x
和 y
是 int
,则 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;
}
最后,如果您为
<<
提供
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 个偶数字符串?!产量:pair(1,2.5)pair(b,2)pair(10,偶数字符串?!)
与
std::array
一样,
std::tuple
是在编译时定义的,因此它可用于生成更可优化的代码(与std::vector
和std::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;
}
一种方法是创建一个函数,将 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++14/17 功能,但没有什么重要的。最难替换的部分是用于检查元组大小的折叠表达式。这可能必须成为递归检查。
std::apply
,如下所示:
auto tup1 = std::make_tuple(1, 'b', -10);
auto tup2 = std::make_tuple(2.5, 2, std::string("even strings?!"));
auto zipped = std::apply([&](auto const & ... xs) {
return std::apply([&](auto const & ... ys) {
return std::make_tuple(std::make_pair(xs, ys)...);
}, tup2);
}, tup1);
希望有帮助。