C++11 的序列压缩函数?

Sequence-zip function for C++11?

提问人:Hooked 提问时间:12/15/2011 最后编辑:catasaurusHooked 更新时间:9/12/2023 访问量:107624

问:

使用新的基于范围的 for 循环,我们可以编写如下代码:

for(auto x: Y) {}

哪个 IMO 是(例如)的巨大改进

for(std::vector<int>::iterator x=Y.begin(); x!=Y.end(); ++x) {}

它可以用来循环两个同时循环吗,就像 Python 的函数一样?对于那些不熟悉 Python 的人,代码:zip

Y1 = [1, 2, 3]
Y2 = [4, 5, 6, 7]
for x1,x2 in zip(Y1, Y2):
    print(x1, x2)

作为输出给出(1,4) (2,5) (3,6)

C++ C++11 序列

评论

0赞 Seth Carnegie 12/15/2011
基于范围只能与一个变量一起使用,所以不能。如果要一次访问两个值,则必须使用类似forstd::pair
4赞 André Caron 12/15/2011
@SethCarnegie:不是直接的,但你可以想出一个函数来返回元组并遍历元组列表。zip()
2赞 Seth Carnegie 12/15/2011
@AndréCaron你是对的,我的“不”是说你不能使用两个变量,而不是说你不能同时迭代两个容器。
0赞 12/15/2011
显然可以得到这种行为,尽管手很长,所以问题真的是:是否有可能同时在两个对象上“自动”?for(;;)
0赞 John McFarlane 6/11/2015
在未来的修订版(希望是C++17)中,STL的大修将包括范围。然后 view::zip 可能会提供首选解决方案。

答:

0赞 Cat Plus Plus 12/15/2011 #1

Boost.Iterators 有您可以使用zip_iterator(例如文档中的示例)。它不适用于范围,但您可以使用和 lambda。std::for_each

评论

0赞 Xeo 12/15/2011
为什么它不适用于基于范围的 for?将它与 Boost.Range 结合使用,您应该可以设置。
0赞 Cat Plus Plus 12/15/2011
@Xeo:我不太了解Range。我想你可以涉及一些样板并使其工作,但 IMO 只是使用会不那么麻烦。for_each
0赞 UncleBens 12/15/2011
你的意思是这样的事情并不麻烦:?std::for_each(make_zip_iterator(make_tuple(Y1.begin(), Y2.begin())), make_zip_iterator(make_tuple(Y1.end(), Y2.end())), [](const tuple<int, int>& t){printf("%d %d\n", get<0>(t), get<1>(t)); });
3赞 UncleBens 12/15/2011
我应该启动一个 Lambda Does Not Make std::for_each Useful 活动。:)
2赞 UncleBens 12/16/2011
@Xeo:这可能应该是一个单独的问题,但为什么哦为什么??
16赞 Alexandre C. 12/15/2011 #2

您可以使用基于 的解决方案。创建一个虚假的容器类,维护对容器的引用,并从 和 成员函数返回。现在你可以写boost::zip_iteratorzip_iteratorbeginend

for (auto p: zip(c1, c2)) { ... }

示例实现(请测试):

#include <iterator>
#include <boost/iterator/zip_iterator.hpp>

template <typename C1, typename C2>
class zip_container
{
    C1* c1; C2* c2;

    typedef boost::tuple<
        decltype(std::begin(*c1)), 
        decltype(std::begin(*c2))
    > tuple;

public:
    zip_container(C1& c1, C2& c2) : c1(&c1), c2(&c2) {}

    typedef boost::zip_iterator<tuple> iterator;

    iterator begin() const
    {
         return iterator(std::begin(*c1), std::begin(*c2));
    }

    iterator end() const
    {
         return iterator(std::end(*c1), std::end(*c2));
    }
};

template <typename C1, typename C2>
zip_container<C1, C2> zip(C1& c1, C2& c2)
{
    return zip_container<C1, C2>(c1, c2);
}

我把可变参数版本留给读者作为一个很好的练习。

评论

3赞 Nicol Bolas 12/15/2011
+1:Boost.Range 可能应该包含这个。事实上,我会就此向他们提出功能请求。
2赞 Alexandre C. 12/15/2011
@NicolBolas:你做得很好。这应该很容易实现 + ,即使是可变参数版本。boost::iterator_rangeboost::zip_iterator
1赞 Jonathan Wakely 1/17/2013
我相信如果范围的长度不同,这永远不会终止(并且具有未定义的行为)。
1赞 Jonathan Wakely 4/21/2013
boost::zip_iterator不适用于不同长度的范围
1赞 Paul 10/29/2015
即使在干净的 c++03 中,这也应该有效,带有 pair 而不是元组。尽管如此,当长度不相等时,这也会产生问题。可以通过获取最小容器的相应 end() 来对 end() 执行某些操作。这似乎在规范中,就像在 OP 问题中一样。
106赞 kennytm 12/15/2011 #3

警告:从 Boost 1.63.0(2016 年 12 月 26 日)开始,如果输入容器的长度不同,将导致未定义的行为(它可能会崩溃或迭代超过末尾)。boost::zip_iteratorboost::combine


从 Boost 1.56.0(2014 年 8 月 7 日)开始,您可以使用 boost::combine (该函数存在于早期版本中,但未记录):

#include <boost/range/combine.hpp>
#include <vector>
#include <list>
#include <string>

int main() {
    std::vector<int> a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::list<std::string> c {"a", "b", "c"};
    for (auto tup : boost::combine(a, b, c, a)) {    // <---
        int x, w;
        double y;
        std::string z;
        boost::tie(x, y, z, w) = tup;
        printf("%d %g %s %d\n", x, y, z.c_str(), w);
    }
}

这将打印

4 7 a 4
5 8 b 5
6 9 c 6

在早期版本中,您可以自己定义一个范围,如下所示:

#include <boost/iterator/zip_iterator.hpp>
#include <boost/range.hpp>

template <typename... T>
auto zip(T&&... containers) -> boost::iterator_range<boost::zip_iterator<decltype(boost::make_tuple(std::begin(containers)...))>>
{
    auto zip_begin = boost::make_zip_iterator(boost::make_tuple(std::begin(containers)...));
    auto zip_end = boost::make_zip_iterator(boost::make_tuple(std::end(containers)...));
    return boost::make_iterator_range(zip_begin, zip_end);
}

用法是一样的。

评论

1赞 gnzlbg 12/13/2012
你能用它来排序吗?即 std::sort(zip(a.begin(),...),zip(a.end(),...),[](tup a, tup b){a.get<0>() > b.get<0>()}); ?
0赞 kennytm 12/13/2012
@gnzlbg:不,你不能
0赞 Yakk - Adam Nevraumont 6/26/2013
我会被元素诱惑,以获得过去的迭代可能性......optional
3赞 Carneiro 7/11/2014
你有没有机会用 std::make_tuple 和 std::tie 做到这一点?我试图在最大程度地减少提升依赖性的同时使用它,但我无法让它工作。
0赞 Catskul 7/6/2019
@kennytm知道他们为什么决定选择UB而不是在最短距离的末端结束吗?
17赞 Jonathan Wakely 1/17/2013 #4

请参阅 <redi/zip.h> 了解一个函数,该函数适用于 range-base 并接受任意数量的范围,这些范围可以是右值或左值,并且可以是不同的长度(迭代将在最短范围的末尾停止)。zipfor

std::vector<int> vi{ 0, 2, 4 };
std::vector<std::string> vs{ "1", "3", "5", "7" };
for (auto i : redi::zip(vi, vs))
  std::cout << i.get<0>() << ' ' << i.get<1>() << ' ';

指纹0 1 2 3 4 5

评论

2赞 kirill_igum 10/30/2014
您还可以使用boost/tuple/tuple_io.hppcout << i;
0赞 Isac Casapu 12/6/2016
这就是对我有用的东西。但是,在我的代码中,我必须使用等效的 和 .我不确定为什么不能直接调整原始示例,这可能与我的代码不断引用容器有关。boost::get<0>(i)boost::get<1>(i)
0赞 einpoklum 12/26/2021
请列出您的依赖项。具体来说,这需要 Boost...
26赞 aaronman 9/13/2013 #5

所以我之前写过这个 zip,当我无聊的时候,我决定发布它,因为它与其他 zip 不同,因为它不使用 boost,看起来更像 c++ stdlib。

template <typename Iterator>
    void advance_all (Iterator & iterator) {
        ++iterator;
    }
template <typename Iterator, typename ... Iterators>
    void advance_all (Iterator & iterator, Iterators& ... iterators) {
        ++iterator;
        advance_all(iterators...);
    } 
template <typename Function, typename Iterator, typename ... Iterators>
    Function zip (Function func, Iterator begin, 
            Iterator end, 
            Iterators ... iterators)
    {
        for(;begin != end; ++begin, advance_all(iterators...))
            func(*begin, *(iterators)... );
        //could also make this a tuple
        return func;
    }

使用示例:

int main () {
    std::vector<int> v1{1,2,3};
    std::vector<int> v2{3,2,1};
    std::vector<float> v3{1.2,2.4,9.0};
    std::vector<float> v4{1.2,2.4,9.0};
     zip (
            [](int i,int j,float k,float l){
                std::cout << i << " " << j << " " << k << " " << l << std::endl;
            },
            v1.begin(),v1.end(),v2.begin(),v3.begin(),v4.begin());
}

评论

4赞 Xeo 9/13/2013
您应该检查是否有任何迭代器在末尾。
1赞 aaronman 9/13/2013
@Xeo所有范围的大小都应与第一个范围相同或更大
0赞 Hooked 9/13/2013
你能解释一下工作原理吗?这是 lambda 函数吗?[](int i,int j,float k,float l)
1赞 Xeo 9/13/2013
一个常见的需求是压缩不同大小的范围,甚至无限范围。
1赞 aaronman 9/13/2013
@Xeo我明白你的意思,只是像这样的 stdlib 函数通常只是假设第一个范围是最小的,这就是我所遵循的模式
5赞 cshelton 1/10/2014 #6

我独立地遇到了同样的问题,并且不喜欢上述任何语法。因此,我有一个简短的头文件,它基本上与提升zip_iterator相同,但有一些宏使语法对我来说更可口:

https://github.com/cshelton/zipfor

例如,您可以做

vector<int> a {1,2,3};
array<string,3> b {"hello","there","coders"};

zipfor(i,s eachin a,b)
    cout << i << " => " << s << endl;

主要的句法糖是我可以命名每个容器中的元素。我还包含一个“mapfor”,它做同样的事情,但用于地图(命名元素的“.first”和“.second”)。

评论

0赞 Hooked 1/10/2014
这很整洁!它是否可以接受任意数量的参数,所有参数都受到您巧妙的模板限制为有限数量?
0赞 cshelton 1/11/2014
目前,它最多只能处理 9 个并行容器。这很容易推进。虽然可变参数宏允许单个“zipfor”宏处理不同数量的参数,但仍然需要为每个参数编写一个单独的宏(要调度到)。请参阅 groups.google.com/forum/?fromgroups=#!topic/comp.std.c/... 和 stackoverflow.com/questions/15847837/...
0赞 coyotte508 5/27/2016
它是否能很好地处理不同大小的参数?(如OP所述)
0赞 cshelton 5/28/2016
@coyotte508,它假定第一个容器的元素数量最少(并忽略其他容器中的额外元素)。修改以不做出这个假设很容易,但是当元素数量匹配时,这会减慢它的速度(目前它并不比手写慢)。
0赞 einpoklum 12/26/2021
数百行代码,大量使用预处理器,需要 std::tuple(所以更多的代码),而且仍然只支持压缩最多 9 个东西。
6赞 squid 8/26/2015 #7
// declare a, b
BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){
    // your code here.
}
5赞 knedlsepp 4/5/2016 #8

如果你有一个兼容C++14的编译器(例如gcc5),你可以使用Ryan Haining提供的cppitertools库,它看起来真的很有前途:zip

array<int,4> i{{1,2,3,4}};
vector<float> f{1.2,1.4,12.3,4.5,9.9};
vector<string> s{"i","like","apples","alot","dude"};
array<double,5> d{{1.2,1.2,1.2,1.2,1.2}};

for (auto&& e : zip(i,f,s,d)) {
    cout << std::get<0>(e) << ' '
         << std::get<1>(e) << ' '
         << std::get<2>(e) << ' '
         << std::get<3>(e) << '\n';
    std::get<1>(e)=2.2f; // modifies the underlying 'f' array
}
-2赞 Andrew 6/21/2016 #9

这是一个不需要提升的简单版本。它不会特别有效,因为它会创建临时值,并且它不会泛化到列表以外的容器上,但它没有依赖关系,并且它解决了最常见的压缩情况。

template<class L, class R>
std::list< std::pair<L,R> >  zip(std::list<L> left, std::list<R> right)
{
auto l = left.begin();
auto r = right.begin();
std::list< std::pair<L,R> > result;
  while( l!=left.end() && r!=right.end() )
    result.push_back( std::pair<L,R>( *(l++), *(r++) ) );
  return result;
}

尽管其他版本更灵活,但通常使用列表运算符的重点是制作一个简单的单行。此版本的优点是常见情况简单。

评论

0赞 Et7f3XIV 4/2/2021
如果实现迭代器,则可以避免创建结果并根据需要返回下一个元素。它需要更多的代码,因为您需要定义 ++ * 等(所有运算符都由for (auto v : containers))
1赞 Andrew 4/3/2021
@Et7f3XIV没错,但看看 16 年安德鲁写的这段代码,我会把它从轨道上炸开,并使用其他答案之一作为起点。
9赞 lorro 7/13/2016 #10

如果您喜欢运算符重载,这里有三种可能性。前两个分别使用 和 作为迭代器;第三个将其扩展到基于范围的 .请注意,并不是每个人都会喜欢这些运算符的定义,因此最好将它们保存在单独的命名空间中,并在您想要使用这些运算符的函数(不是文件)中有一个。std::pair<>std::tuple<>forusing namespace

#include <iostream>
#include <utility>
#include <vector>
#include <tuple>

// put these in namespaces so we don't pollute global
namespace pair_iterators
{
    template<typename T1, typename T2>
    std::pair<T1, T2> operator++(std::pair<T1, T2>& it)
    {
        ++it.first;
        ++it.second;
        return it;
    }
}

namespace tuple_iterators
{
    // you might want to make this generic (via param pack)
    template<typename T1, typename T2, typename T3>
    auto operator++(std::tuple<T1, T2, T3>& it)
    {
        ++( std::get<0>( it ) );
        ++( std::get<1>( it ) );
        ++( std::get<2>( it ) );
        return it;
    }

    template<typename T1, typename T2, typename T3>
    auto operator*(const std::tuple<T1, T2, T3>& it)
    {
        return std::tie( *( std::get<0>( it ) ),
                         *( std::get<1>( it ) ),
                         *( std::get<2>( it ) ) );
    }

    // needed due to ADL-only lookup
    template<typename... Args>
    struct tuple_c
    {
        std::tuple<Args...> containers;
    };

    template<typename... Args>
    auto tie_c( const Args&... args )
    {
        tuple_c<Args...> ret = { std::tie(args...) };
        return ret;
    }

    template<typename T1, typename T2, typename T3>
    auto begin( const tuple_c<T1, T2, T3>& c )
    {
        return std::make_tuple( std::get<0>( c.containers ).begin(),
                                std::get<1>( c.containers ).begin(),
                                std::get<2>( c.containers ).begin() );
    }

    template<typename T1, typename T2, typename T3>
    auto end( const tuple_c<T1, T2, T3>& c )
    {
        return std::make_tuple( std::get<0>( c.containers ).end(),
                                std::get<1>( c.containers ).end(),
                                std::get<2>( c.containers ).end() );
    }

    // implement cbegin(), cend() as needed
}

int main()
{
    using namespace pair_iterators;
    using namespace tuple_iterators;

    std::vector<double> ds = { 0.0, 0.1, 0.2 };
    std::vector<int   > is = {   1,   2,   3 };
    std::vector<char  > cs = { 'a', 'b', 'c' };

    // classical, iterator-style using pairs
    for( auto its  = std::make_pair(ds.begin(), is.begin()),
              end  = std::make_pair(ds.end(),   is.end()  ); its != end; ++its )
    {
        std::cout << "1. " << *(its.first ) + *(its.second) << " " << std::endl;
    }

    // classical, iterator-style using tuples
    for( auto its  = std::make_tuple(ds.begin(), is.begin(), cs.begin()),
              end  = std::make_tuple(ds.end(),   is.end(),   cs.end()  ); its != end; ++its )
    {
        std::cout << "2. " << *(std::get<0>(its)) + *(std::get<1>(its)) << " "
                           << *(std::get<2>(its)) << " " << std::endl;
    }

    // range for using tuples
    for( const auto& d_i_c : tie_c( ds, is, cs ) )
    {
        std::cout << "3. " << std::get<0>(d_i_c) + std::get<1>(d_i_c) << " "
                           << std::get<2>(d_i_c) << " " << std::endl;
    }
}
3赞 foges 12/29/2016 #11

对于我正在编写的 C++ 流处理库,我一直在寻找一种不依赖第三方库并使用任意数量的容器的解决方案。我最终得到了这个解决方案。它类似于使用boost的公认解决方案(如果容器长度不相等,也会导致未定义的行为)

#include <utility>

namespace impl {

template <typename Iter, typename... Iters>
class zip_iterator {
public:
  using value_type = std::tuple<const typename Iter::value_type&,
                                const typename Iters::value_type&...>;

  zip_iterator(const Iter &head, const Iters&... tail)
      : head_(head), tail_(tail...) { }

  value_type operator*() const {
    return std::tuple_cat(std::tuple<const typename Iter::value_type&>(*head_), *tail_);
  }

  zip_iterator& operator++() {
    ++head_; ++tail_;
    return *this;
  }

  bool operator==(const zip_iterator &rhs) const {
    return head_ == rhs.head_ && tail_ == rhs.tail_;
  }

  bool operator!=(const zip_iterator &rhs) const {
    return !(*this == rhs);
  }

private:
  Iter head_;
  zip_iterator<Iters...> tail_;
};

template <typename Iter>
class zip_iterator<Iter> {
public:
  using value_type = std::tuple<const typename Iter::value_type&>;

  zip_iterator(const Iter &head) : head_(head) { }

  value_type operator*() const {
    return value_type(*head_);
  }

  zip_iterator& operator++() { ++head_; return *this; }

  bool operator==(const zip_iterator &rhs) const { return head_ == rhs.head_; }

  bool operator!=(const zip_iterator &rhs) const { return !(*this == rhs); }

private:
  Iter head_;
};

}  // namespace impl

template <typename Iter>
class seq {
public:
  using iterator = Iter;
  seq(const Iter &begin, const Iter &end) : begin_(begin), end_(end) { }
  iterator begin() const { return begin_; }
  iterator end() const { return end_; }
private:
  Iter begin_, end_;
};

/* WARNING: Undefined behavior if iterator lengths are different.
 */
template <typename... Seqs>
seq<impl::zip_iterator<typename Seqs::iterator...>>
zip(const Seqs&... seqs) {
  return seq<impl::zip_iterator<typename Seqs::iterator...>>(
      impl::zip_iterator<typename Seqs::iterator...>(std::begin(seqs)...),
      impl::zip_iterator<typename Seqs::iterator...>(std::end(seqs)...));
}

评论

1赞 javaLover 2/15/2017
链接断了...如果帖子显示如何使用它会很有用,例如 main() ?
0赞 winnetou 12/9/2017
@javaLover:你可以用与@knedlsepp答案中的cppitertools相同的方式使用它。一个值得注意的区别是,使用上述解决方案,您不能修改基础容器,因为 for 返回 const 引用的 a。operator*seq::iteratorstd::tuple
22赞 csguth 4/14/2017 #12

使用 range-v3

#include <range/v3/all.hpp>
#include <vector>
#include <iostream>

namespace ranges {
    template <class T, class U>
    std::ostream& operator << (std::ostream& os, common_pair<T, U> const& p)
    {
      return os << '(' << p.first << ", " << p.second << ')';
    }
}

using namespace ranges::v3;

int main()
{
    std::vector<int> a {4, 5, 6};
    double b[] = {7, 8, 9};
    std::cout << view::zip(a, b) << std::endl; 
}

输出:

[(4, 7),(5, 8),(6, 9)]

36赞 Venki 6/26/2018 #13

std::transform 可以简单地做到这一点:

std::vector<int> a = {1,2,3,4,5};
std::vector<int> b = {1,2,3,4,5};
std::vector<int>c;
std::transform(a.begin(),a.end(), b.begin(),
               std::back_inserter(c),
               [](const auto& aa, const auto& bb)
               {
                   return aa*bb;
               });
for(auto cc:c)
    std::cout<<cc<<std::endl;

如果第二个序列较短,我的实现似乎给出了默认的初始化值。

评论

10赞 Adrian 5/7/2019
如果第二个序列较短,那么我希望这是 UB,因为您将迭代 的末尾。b
1赞 c z 8/17/2021
@Adrian 部分 - 应注意 UB 是由于 ,而不是 。用户应该提供自己的迭代器来处理范围结束,例如,通过引发错误或返回结束的零。vector<>::iteratorstd::transform
2赞 einpoklum 12/26/2021 #14

对 aaronman 解决方案的改进:

  • 仍然是 C++11。
  • 没有递归模板扩展。
  • 支持容器压缩。
  • 利用 Sean Parent 著名的 for_each_arg()的方法。
// Includes only required for the example main() below!
#include <vector>
#include <iostream>

namespace detail {

struct advance {
    template <typename T> void operator()(T& t) const { ++t; }
};

// Adaptation of for_each_arg, see:
// https://isocpp.org/blog/2015/01/for-each-argument-sean-parent
template <class... Iterators>
void advance_all(Iterators&... iterators) {
    [](...){}((advance{}(iterators), 0)...);
}

} // namespace detail

template <typename F, typename Iterator, typename ... ExtraIterators>
F for_each_zipped(
    F func, 
    Iterator begin, 
    Iterator end, 
    ExtraIterators ... extra_begin_iterators)
{
    for(;begin != end; ++begin, detail::advance_all(extra_begin_iterators...))
        func(*begin, *(extra_begin_iterators)... );
    return func;
}
template <typename F, typename Container, typename... ExtraContainers>
F for_each_zipped_containers(
    F func,
    Container& container, 
    ExtraContainers& ... extra_containers)
{
    return for_each_zipped(
        func, std::begin(container), std::end(container), std::begin(extra_containers)...);
}

int main () {
    std::vector<int>   v1 {  1,   2,   3};
    std::vector<int>   v2 {  3,   2,   1};
    std::vector<float> v3 {1.2, 2.4, 9.0};
    std::vector<float> v4 {1.2, 2.4, 9.0};
    auto print_quartet = 
        [](int i,int j,float k,float l) {
            std::cout << i << " " << j << " " << k << " " << l << '\n';
        };
    std::cout << "Using zipped iterators:\n";
    for_each_zipped(print_quartet, v1.begin(), v1.end(), v2.begin(), v3.begin(), v4.begin());
    std::cout << "\nUsing zipped containers:\n";
    for_each_zipped_containers(print_quartet, v1, v2, v3, v4);
}

看到它在 GodBolt 上工作

2赞 Stepan Dyatkovskiy 6/2/2022 #15

我会提出这个。我发现它非常优雅,正是我(和你)所需要的。

https://github.com/CommitThis/zip-iterator

以防万一,这里有一个代码副本。注意,它是在 MIT 许可下分发的,也不要忘记输入作者姓名。

zip.hpp

/***
 * MIT License
 * Author: G Davey
 */

#pragma once

#include <cassert>
#include <functional>
#include <iomanip>
#include <iostream>
#include <list>
#include <string>
#include <vector>
#include <typeinfo>

namespace c9 {

template <typename Iter>
using select_access_type_for = std::conditional_t<
    std::is_same_v<Iter, std::vector<bool>::iterator> ||
    std::is_same_v<Iter, std::vector<bool>::const_iterator>,
    typename Iter::value_type,
    typename Iter::reference
>;


template <typename ... Args, std::size_t ... Index>
auto any_match_impl(std::tuple<Args...> const & lhs,
    std::tuple<Args...> const & rhs,
    std::index_sequence<Index...>) -> bool
{
    auto result = false;
    result = (... | (std::get<Index>(lhs) == std::get<Index>(rhs)));
    return result;
}


template <typename ... Args>
auto any_match(std::tuple<Args...> const & lhs, std::tuple<Args...> const & rhs)
    -> bool
{
    return any_match_impl(lhs, rhs, std::index_sequence_for<Args...>{});
}



template <typename ... Iters>
class zip_iterator
{
public:

    using value_type = std::tuple<
        select_access_type_for<Iters>...
    >;

    zip_iterator() = delete;

    zip_iterator(Iters && ... iters)
        : m_iters {std::forward<Iters>(iters)...}
    {
    }

    auto operator++() -> zip_iterator&
    {
        std::apply([](auto && ... args){ ((args += 1), ...); }, m_iters);
        return *this;
    }

    auto operator++(int) -> zip_iterator
    {
        auto tmp = *this;
        ++*this;
        return tmp;
    }

    auto operator!=(zip_iterator const & other)
    {
        return !(*this == other);
    }

    auto operator==(zip_iterator const & other)
    {
        auto result = false;
        return any_match(m_iters, other.m_iters);
    }

    auto operator*() -> value_type
    {
        return std::apply([](auto && ... args){
                return value_type(*args...);
            }, m_iters);
    }

private:
    std::tuple<Iters...> m_iters;
};


/* std::decay needed because T is a reference, and is not a complete type */
template <typename T>
using select_iterator_for = std::conditional_t<
    std::is_const_v<std::remove_reference_t<T>>,
    typename std::decay_t<T>::const_iterator,
    typename std::decay_t<T>::iterator>;



template <typename ... T>
class zipper
{
public:
    using zip_type = zip_iterator<select_iterator_for<T> ...>;

    template <typename ... Args>
    zipper(Args && ... args)
        : m_args{std::forward<Args>(args)...}
    {
    }

    auto begin() -> zip_type
    {
        return std::apply([](auto && ... args){
                return zip_type(std::begin(args)...);
            }, m_args);
    }
    auto end() -> zip_type
    {
        return std::apply([](auto && ... args){
                return zip_type(std::end(args)...);
            }, m_args);
    }

private:
    std::tuple<T ...> m_args;

};


template <typename ... T>
auto zip(T && ... t)
{
    return zipper<T ...>{std::forward<T>(t)...};
}

}

#include "zip.hpp"
#include <vector>

std::vector<int> a, b, c;

void foo() {
    for (auto && [x, y] : zip(a, b))
        c.push_back(x + z);
}


8赞 Pavan Chandaka 9/13/2022 #16

C++23 开始,我们可以迭代 . 下面是一个简单的例子。std::views::zip

#include <iostream>
#include <ranges>
#include <vector>
 
int main() {
    std::vector<int> x {4, 5, 6};
    double y[] = {7, 8, 9};

    for (auto [elem1,elem2] : std::views::zip(x, y))        
        std::cout << "[" << elem1 << "," << elem2 << "]" << " ";
}

输出可以在下面验证(在线编译器)。不确定链接存在多少天。

https://godbolt.org/z/KjjE4eeGY

评论

0赞 Silicomancer 9/26/2022
对我来说,GCC 12.1 找不到 zip 运算符。“std::views::zip”和“std::ranges::views::zip”,即使启用了 C++23。这是意料之中的吗?有没有支持zip的GCC版本?
0赞 Pavan Chandaka 9/27/2022
上面的链接(在答案中)有选择编译器的选项,你可以用GCC12.2来验证。我迅速检查了他们甚至在 GCC12.2 中也没有实现它
1赞 Duloren 4/25/2023
@Silicomancer据此,GCC 支持仅为 >=13:en.cppreference.com/w/cpp/compiler_support