Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

std::for_each working on more than one range of iterators

Tags:

c++

c++11

stl

boost

The lambda notation has made stl algorithms more accessible. I am still learning to decide when it's useful and when to fall back to good old fashioned for-loops. Often, it becomes necessary to iterate over two (or more) containers of the same size, such that corresponding elements are related, but for some reason are not packed into the same class.

A function using a for-loop to achieve that would look like this:

template<typename Data, typename Property>
void foo(vector<Data>& data, vector<Property>& prop) {
    auto i_data = begin(data);
    auto i_prop = begin(prop);
    for (; i_data != data.end(); ++i_data, ++i_prop) {
        if (i_prop->SomePropertySatistfied()) {
            i_data->DoSomething();
        }
    }
}

In order to use for_each, I need a version of it that handles multiple ranges; something like:

template<typename InputIter1, typename InputIter2, typename Function>
Function for_each_on_two_ranges(InputIter1 first1, InputIter1 last1, InputIter2 first2, Function f) {
    for (; first1 != last1; ++first1, ++first2) {
        f(*first1, *first2);
    }
    return f;
}

With this version, the above code would look like this:

template<typename Data, typename Property>
void foo_two_ranges(vector<Data>& data, vector<Property>& prop) {
    for_each_on_two_ranges(begin(data), end(data), begin(prop), [](Data& d, Property& p) {
        if (p.SomePropertySatistfied()) {
            d.DoSomething();
        }
    });
}

Is there an equivalent way of achieving the same result using stl algorithms?

EDIT

I found the exact answer to my question in the form of boost::for_each running on boost::range. I added the answer, with example code for the sake of completeness.

like image 678
killogre Avatar asked Apr 02 '12 11:04

killogre


3 Answers

1) The algorithms in the STL are not meant to cover every possible case, if you need for_each_on_two_ranges then write it (as you have) and use it. The beauty of the STL is it's so extensible, and you've extended it with a useful new algo.

2) If that doesn't work, you don't have to use good old fashioned for-loops, you can use fancy new for-loops instead!

As another answer said, boost::zip_iterator is your friend here, but it doesn't have to be hard to use. Here's a solution using a range adaptor that is implemented with zip_iterator

template<typename Data, typename Property>
void foo(vector<Data>& data, vector<Property>& prop) {
    for (auto i : redi::zip(data, prop))
        if (i.get<1>().SomePropertySatistfied())
            i.get<0>.DoSomething();
}

That zip function creates an adaptor with begin() and end() members that return a boost::zip_iterator, so the loop variable is a tuple of the elements of each underlying container (and as it's a variadic template you can do it for any number of containers, so you don't need to write for_each_for_three_ranges and for_each_for_four_ranges etc.)

You could also use it with for_each

auto z = redi::zip(data, prop);
typedef decltype(z)::iterator::reference reference;

for_each(begin(z), end(z), [](reference i) {
    if (i.get<1>().SomePropertySatistfied()) {
        i.get<0>().DoSomething();
    }
});
like image 67
Jonathan Wakely Avatar answered Nov 15 '22 16:11

Jonathan Wakely


After reading up on boost::zip_iterator and boost::iterator_range as suggested by some answers, I came across the extension algorithms in boost::range, and found the exact parallel of the algorithm I wrote for two ranges, but with boost ranges.

A working code for the example would be

#include <boost/range/algorithm_ext/for_each.hpp>

template<typename Data, typename Property>
void foo_two_ranges(vector<Data>& data, vector<Property>& prop) {
    auto rng1 = boost::make_iterator_range(data.begin(), data.end());
    auto rng2 = boost::make_iterator_range(prop.begin(), prop.end());
    boost::for_each(rng1, rng2, [](Data& d, Property& p) {
        if (p.SomePropertySatistfied()) {
            d.DoSomething();
        }
    });
}

Some wrappers and utility functions, similar in mind to what @Jonathan Wakely suggested, can make this even more usable.

like image 31
killogre Avatar answered Nov 15 '22 16:11

killogre


std::transform has an overload that operates on two sequences in parallel. You'd need a null output iterator to absorb the results though, if you weren't interested in collecting any.

like image 22
Ben Voigt Avatar answered Nov 15 '22 18:11

Ben Voigt