问题 std :: for_each在多个迭代器范围内工作


lambda表示法使得stl算法更易于访问。我仍然在学习什么时候它有用以及何时回归到老式的for-loops。 通常,有必要迭代两个(或更多)相同大小的容器,使得相应的元素是相关的,但由于某种原因不会被打包到同一个类中。

使用for循环实现的函数如下所示:

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 :: range上以boost :: for_each的形式找到了我的问题的确切答案。为了完整起见,我添加了答案,并附带示例代码。


11745
2018-04-02 11:56


起源

为什么不直接使用 for_each_two_ranges 你已经写过了吗? - Puppy
它看起来像我这么常见,我认为它已经被某人解决了 - killogre
我认为 zip_iterator 来自Boost.Iterator做你想要的。看到 boost.org/doc/libs/1_49_0/libs/iterator/doc/zip_iterator.html 详情。 - celtschk
谢谢celtschk,我相信你是对的。但是使用zip_iterator的开销使我倾向于简单的方案 - killogre
只要迭代器本身是独立的,你就无法合理地将该语义抽象到函数中。它看起来像序列在两个可迭代结构之间有足够的隐含共性,你可以对某种映射做出假设,但是你必须牺牲你为一些语法简单所做的一般性吸引力。我会说,除非你真的这么做,否则你已经提出了一个绰绰有余的解决方案。 - Michael Wilson


答案:


1)如果需要,STL中的算法并不意味着涵盖所有可能的情况 for_each_on_two_ranges 然后写它(如你所有)并使用它。 STL的美妙之处在于它是如此可扩展,你用一个有用的新算法扩展它。

2)如果这不起作用,你不必使用旧的老式for循环,你可以使用花哨的新for循环!

另一个答案说, 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();
}

压缩 function创建一个适配器 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();
    }
});

8
2018-05-04 23:05



这看起来很不错。我喜欢它的变量,并且代码相对紧凑。我看到我需要在范围适配器上做一些功课。谢谢! - killogre
redi是boost中的命名空间吗?什么是必需的标题? Thankee。 - Jive Dadson
@JiveDadson,不,这是我自己的命名空间,我把工作联系起来 压缩 来源(现在又做了) - Jonathan Wakely
@Jonathan原谅我是密集的。我认为你编写了链接的代码。正确? - Jive Dadson
@JiveDadson,是的,正如代码顶部的版权评论中所述。 - Jonathan Wakely


按照一些答案的建议阅读了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建议的相似,可以使它更加实用。


5
2018-05-08 05:51



包含路径应​​始终使用/不是\甚至在Windoze上,否则 "path\names\like\this.hpp" 将使用换行符和TAB字符。此外,它还可以移植到所有系统,而不仅仅是Windoze。 - Jonathan Wakely
该片段现在对Unix友好,@ JonathanWakely :) - killogre
请注意,你可以写 boost::for_each(data, prop, ...)  - 不需要打电话 make_iterator_range。 - ecatmur


std::transform 具有一个并行运行在两个序列上的重载。如果你不想收集任何结果,你需要一个null输出迭代器来吸收结果。


1
2018-04-02 15:37



for_each和transform有不同的语义 链接。标准要求转换为非变异,但不要求for_each。无论如何,你提到的重载仅适用于相同的前向迭代器 - killogre
@Killogre: std::transform 通过将输出迭代器设置为其中一个输入迭代器来允许变异。但是,是的,它并不是真的针对这种情况而设计的。 - Ben Voigt