如何在C++ 11(STL)中创建一个拉链两个元组的函数?

use*_*ser 17 c++ tuples template-meta-programming c++11

我最近碰到了这个难题,终于能够解决一个hacky的答案(使用索引数组),并想分享它(下面的答案).我确信有些答案使用模板递归和使用的答案boost; 如果您有兴趣,请分享其他方法来做到这一点.我认为将这些全部放在一个地方可能会让其他人受益,并且对于学习一些很酷的C++ 11模板元编程技巧很有用.

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

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

你如何创建一个将两个元组"压缩"成异构元组的函数?

std::tuple<
    std::pair<int, double>,
    std::pair<char, int>,
    std::pair<int, std::string> > result =
    tuple_zip( tup1, tup2 );
Run Code Online (Sandbox Code Playgroud)

哪里

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?!"));
Run Code Online (Sandbox Code Playgroud)

use*_*ser 18

首先,快速浏览索引数组:

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;
}
Run Code Online (Sandbox Code Playgroud)

现在使用我们的print_helper函数:

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

但是,打字seq<0,1,2>可能会有点痛苦.所以我们可以使用模板递归来创建一个类来生成seqs,所以它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;
}
Run Code Online (Sandbox Code Playgroud)

由于Nin 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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,模板参数可以自动推导出来(输入所有这些将是一种痛苦不是吗?).

现在,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) )...);
}
Run Code Online (Sandbox Code Playgroud)

代码有点棘手,特别是尾随返回类型(返回类型声明为auto->在定义参数后提供).这让我们避免连的问题定义的返回类型是什么,简单地宣布它会返回在函数体中使用的表达(如果xyintS,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() );
}
Run Code Online (Sandbox Code Playgroud)

现在您可以按照问题中的指定使用它:

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;
}
Run Code Online (Sandbox Code Playgroud)

最后,如果您提供了一个<<操作符,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;
}
Run Code Online (Sandbox Code Playgroud)

输出是:

压缩:1 b 10
配:2.5 2连串?!
产量:对(1,2.5)对(b,2)对(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) );
}
Run Code Online (Sandbox Code Playgroud)

顺便说一下,你现在需要这个才能使用我们的便利print功能:

std::ostream & operator << (std::ostream & os, std::nullptr_t) {
  os << "null_ptr";
  return os;
}
Run Code Online (Sandbox Code Playgroud)

  • 好整体解决方案.我只会说几句话.1 /我会使用`unsigned`作为索引(至少),因为你不期望负数索引,对吗?2 /欢迎`static_assert`而不是`assert`.3 /该对可能应该由`operator <<`重载中的`const &`来取. (3认同)