Std:: for_each работает с несколькими итераторами

Лямбда-нотация сделала алгоритмы stl более доступными. Я все еще участвую в том, чтобы решать, когда это полезно, и когда нужно вернуться к старым старинным петлям. Часто возникает необходимость перебирать два (или более) контейнера одного размера, так что соответствующие элементы связаны, но по какой-то причине не упаковываются в один класс.

Функция, использующая for-loop для достижения этого, будет выглядеть так:

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

Чтобы использовать for_each, мне нужна его версия, которая обрабатывает несколько диапазонов; что-то вроде:

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

В этой версии приведенный выше код будет выглядеть так:

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

Существует ли эквивалентный способ достижения одного и того же результата с помощью stl-алгоритмов?

ИЗМЕНИТЬ

Я нашел точный ответ на мой вопрос в виде boost:: for_each, работающего на boost:: range. Я добавил ответ с примером кода для полноты.

Ответ 1

1) Алгоритмы в STL не предназначены для охвата всех возможных случаев, если вам нужно for_each_on_two_ranges, а затем записать его (как есть) и использовать его. Красота STL настолько расширяема, и вы расширили ее с помощью полезного нового алгоритма.

2) Если это не сработает, вам не нужно использовать старые старомодные for-loops, вместо этого вы можете использовать причудливые новые for-loops!

Как сказал еще один ответ, boost::zip_iterator - ваш друг здесь, но это не должно быть трудно использовать. Здесь решение с использованием адаптера диапазона, реализованного с помощью 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();
}

Эта функция zip создает адаптер с членами begin() и end(), которые возвращают boost::zip_iterator, поэтому переменная цикла является кортежем элементы каждого базового контейнера (и в качестве вариационного шаблона вы можете сделать это для любого количества контейнеров, поэтому вам не нужно писать for_each_for_three_ranges и for_each_for_four_ranges и т.д.)

Вы также можете использовать его с 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();
    }
});

Ответ 2

После прочтения boost:: zip_iterator и boost:: iterator_range, как было предложено некоторыми ответами, я наткнулся на алгоритмы расширения в boost:: range, и нашел точную параллель алгоритма, который я написал для двух диапазонов, но с диапазонами усиления.

Рабочий код для примера будет

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

Некоторые обертки и функции полезности, похожие на то, что предложил @Jonathan Wakely, могут сделать это еще более полезным.

Ответ 3

std::transform имеет перегрузку, которая работает на двух последовательностях параллельно. Вам понадобится нулевой выводтера для поглощения результатов, если вы не заинтересованы в сборе.