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.
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();
}
});
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With