将 std::transform 与 std::back_inserter 一起使用是否有效?

Is it valid to use std::transform with std::back_inserter?

本文关键字:std 是否 一起 有效 transform back inserter      更新时间:2023-10-16

Cppreference有这个用于std::transform的示例代码:

std::vector<std::size_t> ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
[](unsigned char c) -> std::size_t { return c; });

但它也说:

std::transform不保证按顺序应用unary_opbinary_op。要按顺序将函数应用于序列或应用修改序列元素的函数,请使用std::for_each

这大概是为了允许并行实现。然而,std::transform的第三个参数是一个LegacyOutputIterator,它对++r具有以下后置条件:

在此操作之后,r不需要可递增,并且不再需要以前值r的任何副本可取消引用或递增。

所以在我看来,输出的分配必须按顺序进行。它们是否仅仅意味着unary_op的应用程序可能无序,并存储到临时位置,但按顺序复制到输出中?这听起来不像是你想做的事情。

大多数C++库实际上还没有实现并行执行器,但Microsoft已经实现了。我很确定这是相关的代码,我认为它调用这个populate()函数来将迭代器记录到输出块,这肯定不是一个有效的做法,因为LegacyOutputIterator可以通过增加它的副本来失效。

我错过了什么?

1(标准中的输出迭代器要求被完全打破。请参阅LWG2035。

2(如果你使用纯输出迭代器和纯输入源范围,那么算法在实践中几乎无能为力;它别无选择,只能按顺序书写。(但是,假设的实现可以选择特殊情况自己的类型,例如std::back_insert_iterator<std::vector<size_t>>;我不明白为什么任何实现都想在这里这样做,但它是允许这样做的。

3( 标准中的任何内容都不保证transform按顺序应用转换。我们正在研究一个实施细节。

std::transform只需要输出迭代器并不意味着它无法检测到更高的迭代器强度并在这种情况下重新排序操作。事实上,算法一直在调度迭代器强度,并且它们始终对特殊迭代器类型(如指针或矢量迭代器(进行特殊处理。

当标准想要保证一个特定的订单时,它知道如何说(参见std::copy的"从first开始,到last"(。

n4385

§25.6.4转换

template<class InputIterator, class OutputIterator, class UnaryOperation>
constexpr OutputIterator
transform(InputIterator first1, InputIterator last1, OutputIterator result, UnaryOperation op);
template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2, class UnaryOperation>
ForwardIterator2
transform(ExecutionPolicy&& exec, ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 result, UnaryOperation op);
template<class InputIterator1, class InputIterator2, class OutputIterator, class BinaryOperation>
constexpr OutputIterator
transform(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, OutputIterator result, BinaryOperation binary_op);
template<class ExecutionPolicy, class ForwardIterator1, class ForwardIterator2, class ForwardIterator, class BinaryOperation>
ForwardIterator
transform(ExecutionPolicy&& exec, ForwardIterator1 first1, ForwardIterator1 last1, ForwardIterator2 first2, ForwardIterator result, BinaryOperation binary_op);

§23.5.2.1.2back_inserter

template<class Container>
constexpr back_insert_iterator<Container> back_inserter(Container& x);

返回: back_insert_iterator(x(。

§23.5.2.1类模板back_insert_iterator

using iterator_category = output_iterator_tag;

所以std::back_inserter不能与并行版本的std::transform一起使用。 支持使用输入迭代器从其源读取输出迭代器的版本。 由于输入迭代器只能是前递增和后递增的(§23.3.5.2 输入迭代器(,并且只有顺序(非并行(执行,因此必须在它们和输出迭代器之间保持顺序。

所以我错过的是并行版本需要LegacyForwardIterators,而不是LegacyOutputIteratorLegacyForwardIterator可以在不使其副本失效的情况下递增,因此很容易使用它来实现无序并行std::transform

我认为std::transform的非并行版本必须按顺序执行。要么cpp首选项是错误的,要么标准只是隐含了这个要求,因为没有其他方法来实现它。(霰弹枪没有涉足标准找出答案!

我相信转换可以保证按顺序处理std::back_inserter_iterator是一个输出迭代器(其iterator_category成员类型是std::output_iterator_tag的别名(,根据[back.insert.iterator]。

因此,std::transform除了在result参数上调用成员operator++之外,没有其他选择可以继续下一次迭代。

当然,这仅适用于没有执行策略的重载,其中不能使用std::back_inserter_iterator(它不是转发迭代器(。


顺便说一句,我不会与 cpp 偏好的引号争论。那里的陈述往往不精确或简化。在这种情况下,最好查看C++标准。其中,关于std::transform,没有关于操作顺序的引用。

相关文章: