【问题标题】:C++ zip variadic templatesC++ zip 可变参数模板
【发布时间】:2012-05-12 07:39:06
【问题描述】:

这是一个简单的 C++ 中的双容器 zip 函数:

template <typename A, typename B>
std::list<std::pair<A, B> > simple_zip(const std::list<A> & lhs,
                                       const std::list<B> & rhs)
{
  std::list<std::pair<A, B> >  result;
  for (std::pair<typename std::list<A>::const_iterator,
                 typename std::list<B>::const_iterator> iter
       =
       std::pair<typename std::list<A>::const_iterator,
                 typename std::list<B>::const_iterator>(lhs.cbegin(),
                                                        rhs.cbegin());
       iter.first != lhs.end() && iter.second != rhs.end();
       ++iter.first, ++iter.second)
  {
    result.push_back( std::pair<A, B>(*iter.first, *iter.second) );
  }
  return result;
}

如何将其扩展到具有可变参数模板的任意数量的容器?

我希望general_zip 接受lists 的tuple(每个列表可以包含不同的类型)并返回tuples 的list

【问题讨论】:

  • 那个惊人的-long-for-initialization-statement 可以重写为auto iter = std::make_pair(lhs.cbegin(), rhs.cbegin())。你的声明太长了。
  • @fontanini 太棒了,非常感谢。
  • @dirkgently 我已经看到了这个问题,但它有点乱,似乎没有一个成功的结论......你还有什么推荐的吗?

标签: c++ iterator c++11 tuples variadic-templates


【解决方案1】:

看来这应该可行

std::list<std::tuple<>> simple_zip() {
  return {};
}

template <typename ...T>
std::list<std::tuple<T...>> simple_zip(std::list<T>... lst)
{
  std::list<std::tuple<T...>>  result;
  for (int i = 0, e = std::min({lst.size()...}); i != e; i++) {
    result.emplace_back(std::move(lst.front())...);
    [](...){} ((lst.pop_front(), 0)...);
  }
  return result;
}

@Potatoswatter 有一个很好的(IMO)评论,当列表大小不同时,这可能会复制比需要的更多,并且仅使用迭代器会更好,因为 pop_front 所做的比实际需要的要多。我认为以下以更多代码为代价“修复”了迭代器。

template <typename ...T>
std::list<std::tuple<T...>> simple_zip(std::list<T>... lst)
{
  std::list<std::tuple<T...>>  result;
  struct {
    void operator()(std::list<std::tuple<T...>> &t, int c,
             typename std::list<T>::iterator ...it) {
      if(c == 0) return;
      t.emplace_back(std::move(*it++)...);
      (*this)(t, c-1, it...);
    }
  } zip;
  zip(result, std::min({lst.size()...}), lst.begin()...);
  return result;
}

std::list<std::tuple<>> simple_zip() {
  return {};
}

【讨论】:

  • 我假设struct { int dummy; } pops[] = { (lst.pop_front(), 0)... };是强制参数解包?总的来说,非常有趣的解决方案。 +1
  • +1 哇,这真是太优雅了!我非常感动和感激。一个小变化:std::min(lst.size()...) 出现编译器错误; std::min(1,2,3) 不适合我,但std::min({1,2,3}) 可以,所以我添加了花括号。
  • struct { int dummy; } pops[] = { (lst.pop_front(), 0)... }; 巧妙的滥用,我必须记住这一点。
  • 匿名结构不能是std::initializer_list,而是从auto推导出来的:auto pops = { (lst.pop_front(), 0)... };
  • @Potatoswatter 确定。它也可以是具有适当身份别名模板的alias&lt;int[]&gt;{ (lst.pop_front(), 0)... }。或[](...){}((lst.pop_front(), 0)...); (在这一个中,弹出的顺序未指定)。有很多可能性:)
【解决方案2】:

这是对 Johannes 的第一个答案的增量改进。它避免了虚拟struct 并避免复制整个输入列表(尽管这无关紧要,除非一个列表比其他列表短)。我也使它对所有容器都通用。

但它需要一个样板包索引生成器,无论如何这非常有用

template< std::size_t n, typename ... acc >
struct make_index_tuple {
    typedef typename make_index_tuple<
        n - 1,
        std::integral_constant< std::size_t, n - 1 >, acc ...
    >::type type;
};

template< typename ... acc >
struct make_index_tuple< 0, acc ... >
    { typedef std::tuple< acc ... > type; };

“真正的”实现由一个需要上述实用程序输出的简单函数和一个将包映射到元组的接口函数组成。

template< typename ret_t, std::size_t ... indexes, typename lst_tuple >
ret_t simple_zip( std::tuple< std::integral_constant< std::size_t, indexes > ... >,
    lst_tuple const &lst ) {
    ret_t ret;

    auto iters = std::make_tuple( std::get< indexes >( lst ).begin() ... );
    auto ends = std::make_tuple( std::get< indexes >( lst ).end() ... );

    while ( std::max< bool >({ std::get< indexes >( iters )
                            == std::get< indexes >( ends ) ... }) == false ) {
        ret.emplace_back( * std::get< indexes >( iters ) ++ ... );
    }
    return ret;
}

template< typename ... T >
std::list< std::tuple< typename T::value_type ... > >
simple_zip( T const & ... lst ) {
    return simple_zip
        < std::list< std::tuple< typename T::value_type ... > > > (
        typename make_index_tuple< sizeof ... lst >::type(),
        std::tie( lst ... )
    );
}

至少,这让 Johannes 看起来很简单的事情有了一个清晰的认识。这是大多数可变参数模板算法的样子,因为没有tuple 就无法存储类型可变参数状态,并且没有一组索引或元递归函数就无法处理可变参数元组。 (编辑:啊,现在 Johannes 使用尾递归本地函数来定义本地包来完成所有这些工作,而完全没有 tuple。太棒了……如果你能处理所有的函数式编程;v)。)

【讨论】:

  • 好代码。 " std::max ({ std::get( iters )== std::get( ends ) ... }) == false" 哈哈,真聪明。
【解决方案3】:

另一个版本:Johannes 和 Potatoswatter 的混合回答,尽量减少欺骗的数量(不过我很喜欢!)

template <typename C, typename... Its>
void simple_zip_details(C& c, size_t size, Its... its)
{
   for (int i = 0; i < size; i++)
      c.emplace_back(std::move(*its++)...);
}

template <typename... Ts>
std::list<std::tuple<Ts...>> simple_zip(std::list<Ts>... lst)
{
   std::list<std::tuple<Ts...>>  result;
   size_t size = std::min({ lst.size()... });
   simple_zip_details(result, size, lst.begin()...);
   return result;
}

【讨论】:

  • 我没有测试过它的免责声明(我假设你做过),我对你的代码的简洁印象深刻。
猜你喜欢
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
  • 2016-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-04
相关资源
最近更新 更多