1

I have a class which contains some vectors (minimal code here):

class Foo
{
public: 
   Foo() = default;
private:
   void DoStuff(); 

   std::vector<int>&    mBarInt;
   std::vector<double>  mBarDouble;

   int do_something_with_int;
   double do_something_with_double;
};

On my DoStuff() method I have:

void Foo::DoStuff()
{
    for(auto &val : mBarInt)
    {
        // do something here...
        do_something_with_int = val + ....
    }
}

I would like to do something like this, accessing the two vectors on the same for, but (see below after code) ...

for(int i = 0 ; i < mBarInt.size() ; i++)
{
    // do something here...
    do_something_with_int = mBarInt[i] + ....
    do_something_with_double = mBarDouble[i] + .... // I know mBarDouble has the same size has mBarInt
}

... keeping the same c++11 syntax with the: for(auto &val : mBarInt)

Something like this below:

void Foo::DoStuff()
{
    for(auto &val1, auto val2 : mBarInt, mBarDouble) //?????<---- How can I do this?
    {
         // do something with val1 and val2, which I know that will point to different vectors, but on the same index.
    }
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
waas1919
  • 2,365
  • 7
  • 44
  • 76
  • 2
    It's not possible with range-for loops. It *is* possible to do something similar using [`std::transform`](http://en.cppreference.com/w/cpp/algorithm/transform) where you can have two input iterators. More than two, or if you want to use just the values and not "transform" them then it's not that hard to create a custom function to handle it in a similar way to `std::transform`. – Some programmer dude May 18 '17 at 17:05
  • Thanks @Someprogrammerdude. Does the lambda function work for my problem? – waas1919 May 18 '17 at 17:15
  • 1
    Also you can you [sequence-zip](http://stackoverflow.com/a/8513803/1823963) – malchemist May 18 '17 at 17:44

2 Answers2

2
template<class It>
struct indexing_iterator {
  It it = {};
  using value_type = It;
  using reference = value_type;
  It operator*() const { return it; }
  indexing_iterator& operator++() { ++it; return *this; }
  friend bool operator==( indexing_iterator const& lhs, indexing_iterator const& rhs ) {
    return lhs.it == rhs.it;
  }
  friend bool operator!=( indexing_iterator const& lhs, indexing_iterator const& rhs ) {
    return !(lhs==rhs);
  }
};

template<class F, class It_in>
struct transform_iterator_t:indexing_iterator<It_in> {
  F f = {};
  using reference = decltype(std::declval<F&>()( *std::declval<It_in&>() ) );
  using value_type = std::decay_t<reference>;
  using pointer = value_type*;

  reference operator*() { return f(*this->it); }
  transform_iterator_t( F f_in, It_in it_in ):
    indexing_iterator<It_in>{it_in},
    f(f_in)
  {}
};
template<class F, class It_in>
transform_iterator_t<F,It_in> transform_iterator(
  F f, It_in it
) {
  return {f, it};
}

indexing_iterator<std::size_t> index( std::size_t n ) {
  return {n};
}

template<class It>
struct range_t {
  It b,e;
  It begin() const { return b; }
  It end() const { return b; }
};
template<class It>
range_t<It> range( It b, It e ) { return {b,e}; }

auto range_upto( std::size_t n ) {
  return range( index(0), index(n) );
}

template<class C>
auto to_range( C&& c ) {
  using std::begin; using std::end;
  return range( begin(c), end(c) );
}

template<class It, class F>
range_t< transform_iterator< F, It > >
transform_range( F f, range_t<It> range ) {
  return {
    transform_iterator( f, range.begin() ),
    transform_iterator( f, range.end() )
  };
}

template<class F>
auto generator_range( F f, std::size_t count ) {
  return transform_range( f, range_upto(count) );
}

template<class C1, class C2>
auto zip_over( C1& c1, C2& c2 ) {
  return generator_range(
    [&](std::size_t i) {
      return std::tie( c1[i], c2[i] );
    },
    (std::min)(c1.size(), c2.size())
  );
}

then in C++17 we have

void Foo::DoStuff() {
  for(auto&&[val1, val2] : zip_over(mBarInt, mBarDouble)) {
  }
}

or you can manually unpack the tuple in C++14.

Seems like a lot of work.

There are implementations of zip and the like in boost and other libraries, including Rangev3.

Code not tested. Design should be sound.

There is probably a shorter way to do it more directly, but I find generator via transform and indexing, and zip via generator, easier to understand and write with fewer bugs.

An indexing_iterator takes an incrementable comparable object, and iterates over it. The It can be an integer-like or an interator.

A transform_iterator takes an iterator and a transformation, and returns the transformation on each element.

Neither are full iterators; I skipped the boilerplate. But they are enough for for(:) loops.

A range holds 2 iterators and exposes them to for(:).

An indexing_iterator<std::size_t> is otherwise known as a counting iterator.

A generator iterator is a transformation of a counting iterator. It takes a function that takes an index, and generates an element for that index.

We zip over two random access containers by generating a generator iterator that calls operator[] on both, then returns a tuple. Fancier zipping handles non-random access containers.

The zip of two containers then lets you iterate in parallel over two different containers.

I then use C++17 structured bindings in the for(:) statement to unpack them into two different variables. We could instead just manually unpack as the first 2 statements in our for loop.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
1

You can use boost::make_iterator_range and boost::zip_iterator like this:

std::vector<int>     ints{1,2,3,4,5};
std::vector<double>  doubles{1,2,3,4,5.1};

int do_something_with_int{0};
double do_something_with_double{0};

for (const auto& ref : 
        boost::make_iterator_range(
            boost::make_zip_iterator(boost::make_tuple(ints.cbegin(), doubles.cbegin())),
            boost::make_zip_iterator(boost::make_tuple(ints.cend(), doubles.cend()))))
{
    do_something_with_int   += boost::get<0>(ref);
    do_something_with_double+= boost::get<1>(ref);
}

Live example.

Dev Null
  • 4,731
  • 1
  • 30
  • 46