12

I need a piece of advice for the following situation - I can't figure it out for hours: How to walk through more than one seq . containers of same size (here: two vectors) in a simple way?

int main() {
  int size = 3;
  std::vector<int> v1{ 1, 2, 3 }, v2{ 6, 4, 2 };

  // old-fashioned - ok
  for (int i = 0; i < size; i++) {
    std::cout << v1[i] << " " << v2[i] << std::endl;
  }

  // would like to do the same as above with auto range-for loop
  // something like this - which would be fine for ONE vector.
  // But this does not work. Do I need a hand-made iterator instead?
  for (const auto& i:v1,v2) {
    std::cout << i << " " << i << std::endl;
  }

  return EXIT_SUCCESS;
}

Thank you!

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
Excalibur
  • 286
  • 3
  • 11
  • First I thought it's a dupe, but seems it isn't. – Sebastian Mach Mar 03 '14 at 08:45
  • possible duplicate of [What's the best way to iterate over two or more containers simultaneously](http://stackoverflow.com/questions/12552277/whats-the-best-way-to-iterate-over-two-or-more-containers-simultaneously) – TemplateRex Mar 03 '14 at 10:45

2 Answers2

15

There is boost::combine() in Boost.Range that allows one to write

#include <iostream>
#include <iterator>
#include <vector>
#include <boost/range/combine.hpp>

int main()
{
    std::vector<int> v1{ 1, 2, 3 }, v2{ 6, 4, 2 };

    for (auto&& t : boost::combine(v1, v2))
            std::cout << t.get<0>() << " " << t.get<1>() << "\n";    
}

Live Example.

If you don't like to rely on this, you can spell out the combine() functionality yourself with Boost.Iterator's zip_iterator and Boost.Range's iterator_range and a little bit of C++14 deduced return-types:

template<class... Ranges>
auto combine(Ranges const&... ranges) 
// add -> decltype( boost::make_iterator_range(...) ) in C++11
{
    return boost::make_iterator_range(
        boost::make_zip_iterator(boost::make_tuple(begin(ranges)...)),
        boost::make_zip_iterator(boost::make_tuple(end(ranges)...))        
    );
}

Live Example.

Explanation: boost::make_zip_iterator creates a boost::tuple of iterators into your input ranges, and overloads all the usual operator++ and operator* that you know and love from regular iterators. The iterator_range then wraps two of these zip_iterators into a package with a begin() and end() function that allows it to be used by the C++11 range-for loop. It generalizes to more than two input ranges as well. You can unpack the K-th element from a tuple with the .get<K> member function.

zett42
  • 25,437
  • 3
  • 35
  • 72
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • 2
    Ah, Boost... "simplifying" your code since 1999 :-) – Kerrek SB Mar 03 '14 at 09:10
  • @KerrekSB with `iterator_range` you can hide most of the complexities and use the range-for loop as well. For tuples of multiple ranges, I think it's a net win, but I guess that is a matter of taste for you. – TemplateRex Mar 03 '14 at 09:14
  • 2
    Well, I see the merit, and a zip iterator (or range) is something very fundamental that really should exist. I just wish you could spell it like `begin(v1, v2)` or so... – Kerrek SB Mar 03 '14 at 09:22
  • 2
    @KerrekSB: Actually, I wish I could just say `for (auto&& t: zip(v1, v2))`; `zip` is well-known in about... all functional languages. Of course, the real issue is that we have no tuple unpacking in C++ (since tuples are a library feature, not a language feature) so the usability `get<0>(t)` is a bit down for the count. There is however a significant question in the design of a `zip` method: how to handle sequences of varying length ? And I don't know how Boost handles this case (typical options are: stop on shortest, pad with some given element, pad with null). – Matthieu M. Mar 03 '14 at 09:25
  • 1
    @MatthieuM. see the updated answer with `boost::combine` that does exactly that! – TemplateRex Mar 03 '14 at 09:28
  • @MatthieuM.: With difficulty ... that's a problem of the "zip" concept, though. I think it'd be reasonable to either have it be UB (just keep advancing), or thrown an exception or so... – Kerrek SB Mar 03 '14 at 09:28
  • @TemplateRex: Awesome, now *that's* something I can get behind. – Kerrek SB Mar 03 '14 at 09:29
  • 2
    @TemplateRex: Awesome! I checked the behavior with inputs of unequal length and it's a mixed bag: [it computes the distance](http://coliru.stacked-crooked.com/a/1a5c6653d020f874) to protect against the 2nd sequence being shorter than the 1st, but the other way around [it's not as nice](http://coliru.stacked-crooked.com/a/cccc08dba54735f1). – Matthieu M. Mar 03 '14 at 13:08
  • @MatthieuM. I don't think that's really bad. For any iterator, incrementing/dereferencing past-the-end is UB and users can add these checks themselves. Maybe a DEBUG impl could do it for them, but otherwise there is no real problem. – TemplateRex Mar 03 '14 at 15:46
  • 1
    @TemplateRex: Well, it seems annoying in that `zip_iterator` knows all about the ends; so we could have expected a smarter behaviour. – Matthieu M. Mar 03 '14 at 15:49
  • [Some more examples](https://stackoverflow.com/a/38570107/7571258), including C++1z syntactic sugar :) – zett42 Sep 08 '17 at 16:46
14

The range-based for loop was designed as a convenience for iterating one range, because it's by far the most common case. If you need to iterate multiple ranges, which is not that most common case, you can still do it the traditional way:

for (auto i1 = begin(v1), i2 = begin(v2), e = end(v1); i1 != e; ++i1, ++i2)
{
  // processing
}
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • 2
    +1 ... not everything needs to be shoehorned into one particular language feature. The evolution of C++ places particular emphasis on useful features that work well together; one new feature doesn't drive out another, but rather they all work to make the language better. – Kerrek SB Mar 03 '14 at 09:12
  • There is actually an active request in the Core language working group to support syntax to iterate simultaneously on many sequence with range-based for loop, so this is not far-fetched at all (but clearly not for C++14), see http://cplusplus.github.io/EWG/ewg-active.html#43 – Arzar Mar 03 '14 at 13:22
  • 3
    Shouldn't it be `e1 = end(v1), e2 = end(v2); i1 != e1 && i2 != e2`? I realize in this case the two vectors happen to have the same length, but should the `for` loop be written to depend on that? – David Conrad Mar 03 '14 at 18:59
  • 2
    @DavidConrad The OP states "more than one seq . containers of same size" in the question, so I worded the answer accordingly. In the general case, there are multiple ways to deal with this - make same length a precondition, terminate on shortest, test it in advance, ... – Angew is no longer proud of SO Mar 03 '14 at 20:01