如何使一个功能拉链两个元组在C ++ 11(STL)? [英] How to make a function that zips two tuples in C++11 (STL)?

查看:139
本文介绍了如何使一个功能拉链两个元组在C ++ 11(STL)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我最近遇到了这个谜题,终于能够挣扎出一个hacky答案(使用索引数组),并想分享它(回答下面)。我相信有答案使用模板递归和答案,使用 boost ;如果您有兴趣,请分享其他方法。我认为这些都在一个地方可以有益于其他人,并有助于学习一些酷的C ++ 11模板元编程技巧。



问题:
给定两个相等长度的元组:

  auto tup1 = std :: make_tuple ,'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 ?!


解决方案

 模板< std :: size_t ... S& 
struct seq {};

//现在示例如何使用索引数组来打印元组:
template< typename ... T,std :: size_t ... S>
void print_helper(std :: tuple< T ...> tup,seq< S ...> s){
//此技巧非常有用:
// (std :: cout //并返回0.
// {0 ...}展开(因为表达式中有一个S),
//返回一个长度为sizeof ...(S)的数组。
//数组不被使用,但它是一个伟大的黑客做一个操作
//对于每个std :: size_t在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& / code>可能有点痛苦,但。因此,我们可以使用模板递归创建一个类来生成 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 ...>类型;
};

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

由于 N gens< n> :: type 将始终是元组中的元素数量,可以将 print_helper make it easier:

 模板< 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< 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"(t2))...)){
return std :: make_tuple(std :: make_pair(std :: get"(t1),std :: get"(t2))...)
}

代码有点棘手,特别是尾部返回类型在定义参数后声明为 auto ,并提供 - > )。这样,我们可以通过简单地声明返回在函数体中使用的表达式来避免甚至定义返回类型的问题(如果 x y int s, delctype(x + y)在编译时被解析为 int )。



现在将它包装在一个函数中,该函数提供了适当的 seq< 0,1 ... N& code> 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),元组大小必须相同);
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);

//这也是等价的:
// auto x = tuple_zip(tup1,tup2);

return 0;
}

最后,如果您提供< ; std :: pair 运算符可以使用上面定义的打印函数打印压缩结果:

 模板< typename A,typename B> 
std :: ostream&运算符<< (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<< yield:;
print(x);

return 0;
}

输出为:


zipping:1 b 10

with:2.5 2 even strings ?!

yield:pair(1,2.5)pair(b,2 )pair(10,even strings ?!)


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&
}

template< std :: size_t N,template< typename ...>类Tup,类型名... 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(第一,第二)(第一)>(第二)?(第一) - (第二):0)

< 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
}



现在, code> print
function:

  std :: ostream&运算符<< (std :: ostream& os,std :: nullptr_t){
os<< null_ptr;
return os;
}


I recently ran across this puzzle, was finally able to struggle out a hacky answer (using index arrays), and wanted to share it (answer below). I am sure there are answers that use template recursion and answers that use boost; if you're interested, please share other ways to do this. I think having these all in one place may benefit others and be useful for learning some of the cool C++11 template metaprogramming tricks.

Problem: Given two tuples of equal length:

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

How do you create a function that will "zip" the two tuples into a heterogeneous tuple of pairs?

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

Where

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?!"));

解决方案

First, a quick overview of index arrays:

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;
}

And now to use our print_helper function:

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

Typing seq<0,1,2> can be a bit of a pain, though. So we can use template recursion to create a class to generate seqs, so that gens<3>::type is the same as 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;
}

Since the N in gens<N>::type will always be the number of elements in the tuple, you can wrap print_helper to make it easier:

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;
}

Note that the template arguments can be deduced automatically (typing all of that out would be a pain wouldn't it?).

Now, the tuple_zip function:

As before, start with the helper function:

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) )...);
}

The code is a little tricky, particularly the trailing return type (the return type is declared as auto and provided with -> after the parameters are defined). This lets us avoid the problem of even defining what the return type will be, by simply declaring it returns the expression used in the function body (if x and y are ints, delctype(x+y) is resolved at compile time as int).

Now wrap it in a function that provides the appropriate seq<0, 1...N> using 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() );
}

Now you can use it as specified in the question:

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;
}

And finally, if you provide a << operator for std::pair you can use the print function we defined above to print the zipped result:

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;
}

The output is:

zipping: 1 b 10
with : 2.5 2 even strings?!
yields : pair(1,2.5) pair(b,2) pair(10,even strings?!)

Like std::array, std::tuple is defined at compile time, and so it can be used to generate more optimizable code (more information is known at compile time compared to containers like std::vector and std::list). So even though it's sometimes a bit of work, you can sometimes use it to make fast and clever code. Happy hacking!


Edit:

As requested, allowing tuples of different sizes and padding with null pointers:

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) );
}

And BTW, you're going to need this now to use our handy print function:

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

这篇关于如何使一个功能拉链两个元组在C ++ 11(STL)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆