144

With the new range-based for-loop we can write code like:

for(auto x: Y) {}

Which IMO is a huge improvement from (for ex.)

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

Can it be used to loop over two simultaneous loops, like Python's zip function? For those unfamiliar with Python, the code:

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

Gives as output (1,4) (2,5) (3,6)

catasaurus
  • 933
  • 4
  • 20
Hooked
  • 84,485
  • 43
  • 192
  • 261
  • Range-based `for` can only be used with one variable, so no. If you wanted to access two values at a time, you'd have to use something like `std::pair` – Seth Carnegie Dec 14 '11 at 20:17
  • 4
    @SethCarnegie: not directly, but you could come up with a `zip()` function that returns tuples and iterate over the list of tuples. – André Caron Dec 14 '11 at 20:18
  • 2
    @AndréCaron you're right, my "no" was meant to say that you can't use two variables, not that you can't iterate over two containers at once. – Seth Carnegie Dec 14 '11 at 20:20
  • Clearly `for(;;)` can get this behavior, albeit long-hand, so is the question really: Is it possible to for "auto" over two objects at once? –  Dec 14 '11 at 20:21
  • In a future revision (hopefully C++17), an overhaul of the STL will include [ranges](https://github.com/ericniebler/range-v3). Then [view::zip](https://github.com/ericniebler/range-v3/blob/master/include/range/v3/view/zip.hpp) may provide the preferred solution. – John McFarlane Jun 10 '15 at 21:29
  • In the near future (c++ 2023), this will supported: https://en.cppreference.com/w/cpp/ranges/zip_view – Arthur Bricq Nov 14 '22 at 13:11

16 Answers16

102

Warning: boost::zip_iterator and boost::combine as of Boost 1.63.0 (2016 Dec 26) will cause undefined behavior if the length of the input containers are not the same (it may crash or iterate beyond the end).


Starting from Boost 1.56.0 (2014 Aug 7) you could use boost::combine (the function exists in earlier versions but undocumented):

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

This would print

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

In earlier versions, you could define a range yourself like this:

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

The usage is the same.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
  • 1
    could you use this for sorting? i.e. std::sort(zip(a.begin(),...),zip(a.end(),...),[](tup a, tup b){a.get<0>() > b.get<0>()}); ? – gnzlbg Dec 13 '12 at 09:35
  • @gnzlbg: [No you can't](http://stackoverflow.com/questions/9343846/boost-zip-iterator-and-stdsort). – kennytm Dec 13 '12 at 12:08
  • I would be tempted by `optional` elements for past-the-end iteration possibilities... – Yakk - Adam Nevraumont Jun 25 '13 at 22:25
  • 3
    Any chance you can do this with std::make_tuple and std::tie ? I was trying to use this while minimizing the boost dependency but I couldn't make it work. – Carneiro Jul 11 '14 at 05:45
  • @kennytm any idea why they decided to go with UB instead of just ending at the end of the shortest range in the bunch? – Catskul Jul 06 '19 at 00:48
36

std::transform can do this trivially:

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;

If the second sequence is shorter, my implementation seems to be giving default initialized values.

Ben Fulton
  • 3,988
  • 3
  • 19
  • 35
Venki
  • 417
  • 4
  • 7
  • 10
    If the 2nd sequence is shorter, then I'd expect that this is UB as you would be iterating off the end of `b`. – Adrian May 06 '19 at 17:57
  • 1
    @Adrian Partly - Should note that the UB is due to `vector<>::iterator`, not `std::transform`. The user should provide their own iterator to handle end-of-range if they expect it, e.g. by raising an error or returning zeros past the end. – c z Aug 17 '21 at 13:28
25

So I wrote this zip before when I was bored, I decided to post it because it's different than the others in that it doesn't use boost and looks more like the 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;
    }

Example use:

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());
}
aaronman
  • 18,343
  • 7
  • 63
  • 78
  • 4
    You should check if *any* of the iterators is at the end. – Xeo Sep 12 '13 at 18:31
  • 1
    @Xeo all the ranges should be the same size as the first or greater – aaronman Sep 12 '13 at 18:39
  • Can you explain how `[](int i,int j,float k,float l)` works? Is this a lambda function? – Hooked Sep 12 '13 at 18:49
  • @Hooked yeah it's a lambda, it basically works just `std::for_each` but you can use an arbitrary number of ranges, the parameters in the lambda depend on how many iterators you give the function – aaronman Sep 12 '13 at 18:52
  • @Xeo could you explain your criticism if you still think it's valid, I would like to fix it – aaronman Sep 12 '13 at 19:22
  • 1
    A common need is to zip ranges of different size, or even with infinite ranges. – Xeo Sep 12 '13 at 19:23
  • 1
    @Xeo I see your point, it's just that stdlib functions like this usually just assume the first range is the smallest, that was the pattern I was going by – aaronman Sep 12 '13 at 19:25
  • The stdlib unfortunately doesn't always make the smartest choices. ;) – Xeo Sep 12 '13 at 20:39
  • "like the c++ stdlib" Do you mind to show an example of stdlib? I am curious - never seen such function. (I am new to C++) – javaLover Feb 16 '17 at 02:18
  • I've [posted](https://stackoverflow.com/a/70482642/1593077) a slight improvement - or what I believe is an improvement - to this answer; would you take a look? – einpoklum Dec 25 '21 at 20:52
22

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

The output:

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

csguth
  • 569
  • 3
  • 18
17

See <redi/zip.h> for a zip function which works with range-base for and accepts any number of ranges, which can be rvalues or lvalues and can be different lengths (iteration will stop at the end of the shortest range).

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>() << ' ';

Prints 0 1 2 3 4 5

knedlsepp
  • 6,065
  • 3
  • 20
  • 41
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 2
    you can also use `boost/tuple/tuple_io.hpp` to `cout << i;` – kirill_igum Oct 30 '14 at 02:51
  • This is what worked for me. However, in my code I had to use the equivalent of `boost::get<0>(i)` and `boost::get<1>(i)`. I'm not sure why the original sample could not be adapted directly, it might have to do with the fact that my code takes constant references to containers. – Isac Casapu Dec 06 '16 at 12:58
  • Please list your dependencies. Specifically, this requires Boost... – einpoklum Dec 25 '21 at 21:03
16

You can use a solution based on boost::zip_iterator. Make a phony container class maintaining references to your containers, and which return zip_iterator from the begin and end member functions. Now you can write

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

Example implementation (please test):

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

I leave the variadic version as an excellent exercise to the reader.

Alexandre C.
  • 55,948
  • 11
  • 128
  • 197
  • 3
    +1: Boost.Range should probably incorporate this. In fact, I'll drop them a feature request on this. – Nicol Bolas Dec 14 '11 at 20:50
  • 2
    @NicolBolas: You do well. This should be quite easy to implement with `boost::iterator_range` + `boost::zip_iterator`, even the variadic version. – Alexandre C. Dec 14 '11 at 20:53
  • 1
    I believe this will never terminate (and have undefined behaviour) if the ranges are not the same length. – Jonathan Wakely Jan 17 '13 at 15:21
  • @JonathanWakely: Good point. However, I believe that `boost::zip_iterator` does the Right Thing here. If it does not, then the code above may need more work indeed. – Alexandre C. Apr 10 '13 at 18:51
  • 1
    `boost::zip_iterator` does not work with ranges of different lengths – Jonathan Wakely Apr 21 '13 at 15:19
  • 1
    This should also work even in clean c++03 with pair instead of tuple. Still this wil also create problems when the lengths are not equal. Something might be done with the end() by taking the corresponding end() of the smallest container. This seems to be in the spec as it was in OPs question. – Paul Oct 29 '15 at 15:15
9

If you like operator overloading, here are three possibilities. The first two are using std::pair<> and std::tuple<>, respectively, as iterators; the third extends this to range-based for. Note that not everyone will like these definitions of the operators, so it's best to keep them in a separate namespace and have a using namespace in the functions (not files!) where you'd like to use these.

#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;
    }
}
lorro
  • 10,687
  • 23
  • 36
6
// declare a, b
BOOST_FOREACH(boost::tie(a, b), boost::combine(list_of_a, list_of_b)){
    // your code here.
}
Infinite Recursion
  • 6,511
  • 28
  • 39
  • 51
squid
  • 2,597
  • 1
  • 23
  • 19
5

I ran into this same question independently and didn't like the syntax of any of the above. So, I have a short header file that essentially does the same as the boost zip_iterator but has a few macros to make the syntax more palatable to me:

https://github.com/cshelton/zipfor

For example you can do

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

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

The main syntactic sugar is that I can name the elements from each container. I also include a "mapfor" that does the same, but for maps (to name the ".first" and ".second" of the element).

cshelton
  • 360
  • 3
  • 8
  • This is neat! Can it take an arbitrary number of arguments are all those limited by your clever templating to a finite number? – Hooked Jan 10 '14 at 02:42
  • Currently it only handles up to 9 parallel containers. That would be simple to advance. While variadic macros allow for a single "zipfor" macro to handle different numbers of parameters, one still has to code up a separate macro for each (to be dispatched to). See https://groups.google.com/forum/?fromgroups=#!topic/comp.std.c/d-6Mj5Lko_s and http://stackoverflow.com/questions/15847837/variadac-macro-apply-macro-to-all-arguments – cshelton Jan 10 '14 at 17:25
  • Does it handle arguments of different size well? (as described in the OP) – coyotte508 May 27 '16 at 09:15
  • @coyotte508, it assumes that the first container has the fewest number of elements (and ignores the extra elements in other containers). It would be easy to modify to not make this assumption, but that would slow it down (currently it is no slower than hand-written) when the number of elements match. – cshelton May 27 '16 at 16:26
  • Hundreds of lines of code, heavy use of the preprocessor, needs std::tuple (so even more code), plus still only supports zipping up to 9 things. – einpoklum Dec 25 '21 at 21:01
5

If you have a C++14 compliant compiler (e.g. gcc5) you can use zip provided in the cppitertools library by Ryan Haining, which looks really promising:

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
}
knedlsepp
  • 6,065
  • 3
  • 20
  • 41
5

From C++23, we can iterate on std::views::zip. Below is simple example.

#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 << "]" << " ";
}

The output can be verified below (an online compiler). Not sure how many days the link exists.

https://godbolt.org/z/KjjE4eeGY

Pavan Chandaka
  • 11,671
  • 5
  • 26
  • 34
  • For me GCC 12.1 doesn't find zip operator. Neither "std::views::zip" nor "std::ranges::views::zip" even with C++23 enabled. Is this expected? Is there a GCC version that supports zip? – Silicomancer Sep 26 '22 at 10:55
  • Hmmm. the above link (in answer) has options to select the compiler, may be you can verify with GCC12.2. I quickly checked looks like they did not implement it even in GCC12.2 – Pavan Chandaka Sep 27 '22 at 03:41
  • 1
    @Silicomancer according to this, GCC support is only >=13: https://en.cppreference.com/w/cpp/compiler_support – Duloren Apr 24 '23 at 16:49
3

For a C++ stream processing library I'm writing I was looking for a solution that doesn't rely on third party libraries and works with an arbitrary number of containers. I ended up with this solution. It's similar to the accepted solution which uses boost (and also results in undefined behavior if the container lengths are not equal)

#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)...));
}
Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
foges
  • 1,239
  • 1
  • 12
  • 16
  • 1
    link broken... would be useful if the post shows how to use it e.g. main() ? – javaLover Feb 15 '17 at 11:33
  • @javaLover: you can use it the same way as cppitertools in @knedlsepp's answer. One notable difference is that with the above solution you can not modify the underlying containers as the `operator*` for `seq::iterator` returns a `std::tuple` of const references. – winnetou Dec 09 '17 at 13:34
2

An improvement on aaronman's solution:

  • Still C++11.
  • No recursive template expansion.
  • Support for container zipping.
  • Utilizes the approach of Sean Parent's famed 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_iterators)
{
    for(;begin != end; ++begin, detail::advance_all(extra_iterators...))
        func(*begin, *(extra_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);
}

See it working on GodBolt.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
2

I would propose this one. I found it to be quite elegant, and exactly what I (and you) needed.

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

Just in case here's a code copy. Note, it is distributed under MIT License, also don't forget to put name of author.

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

}

Example

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


0

Boost.Iterators has zip_iterator you can use (example's in the docs). It won't work with range for, but you can use std::for_each and a lambda.

Cat Plus Plus
  • 125,936
  • 27
  • 200
  • 224
  • Why won't it work with range-based for? Combine it with Boost.Range and you should be set. – Xeo Dec 14 '11 at 20:22
  • @Xeo: I don't know Range too well. I guess you could involve some boilerplate and make it work, but IMO just using `for_each` would be less hassle. – Cat Plus Plus Dec 14 '11 at 20:26
  • You mean something like this is not hassle: `std::for_each(make_zip_iterator(make_tuple(Y1.begin(), Y2.begin())), make_zip_iterator(make_tuple(Y1.end(), Y2.end())), [](const tuple& t){printf("%d %d\n", get<0>(t), get<1>(t)); });`? – UncleBens Dec 14 '11 at 23:35
  • @UncleBens: "Less hassle", not "not hassle". :P Again, I don't know Range very well, maybe it'll look better after all. – Cat Plus Plus Dec 14 '11 at 23:38
  • 3
    I should start a Lambda Does **Not** Make std::for_each Useful campaign. :) – UncleBens Dec 14 '11 at 23:39
  • @UncleBens: Even C++ gurus (like Herb Sutter) state that `std::for_each` with a lambda is to be preferred over range-based for in most cases. – Xeo Dec 15 '11 at 01:03
  • 2
    @Xeo: This should probably be a separate question, but why oh why?? – UncleBens Dec 15 '11 at 16:55
-2

Here is a simple version that does not require boost. It won't be particularly efficient as it creates temporary values, and it does not generalise over containers other than lists, but it has no dependencies and it solves the most common case for zipping.

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

Although the other versions are more flexible, often the point of using a list operator is make a simple one-liner. This version has the benefit that the common-case is simple.

Andrew
  • 2,943
  • 18
  • 23
  • If you implement an iterator then you could avoid creation of result and return as needed the next element. It need a bit more code as you need to define ++ * etc (all operator used by `for (auto v : containers)`) – Et7f3XIV Apr 02 '21 at 08:50
  • 1
    @Et7f3XIV True, but looking at this code that '16 Andrew wrote I would nuke it from orbit and use one of the other answers as a starting point. – Andrew Apr 03 '21 at 09:20