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

时间:2019-12-11 10:52:14

标签: c++ stl language-lawyer c++17

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已实现。我很确定this是相关的代码,并且我认为它调用this populate() function来将迭代器记录到输出的大块中,这对之所以这样做,是因为LegacyOutputIterator可以通过增加其副本来使其无效。

我想念什么?

4 个答案:

答案 0 :(得分:9)

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

2)如果您使用纯输出迭代器和纯输入源范围,则该算法在实践中几乎无能为力。它只能按顺序写。 (但是,假设的实现可以选择对自己的类型进行特殊化处理,例如std::back_insert_iterator<std::vector<size_t>>;我不明白为什么在这里有任何实现都希望这样做,但允许这样做。)

3)标准中的任何内容都不能保证transform按顺序应用转换。我们正在查看实施细节。

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

当标准要保证特定顺序时,它知道如何说(请参见std::copy的“从first开始并进行到last”)。

答案 1 :(得分:5)

来自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.2 back_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节输入迭代器),并且只能顺序执行( ie 非并行),因此必须在它们和输出迭代器之间保留顺序。

答案 2 :(得分:0)

所以我想念的是并行版本采用LegacyForwardIterator,而不是LegacyOutputIterator。可以增加LegacyForwardIterator 而不会使它的副本无效,因此很容易使用它来实现无序并行std::transform

我认为std::transform 的非并行版本必须按顺序执行。要么cppreference是错误的,否则标准可能只是将该要求保留为隐式,因为没有其他方法可以实现它。 (S弹枪不涉猎标准!)

答案 3 :(得分:0)

我相信,转换可以按顺序进行处理。根据{{​​3}},std::back_inserter_iterator输出迭代器(其iterator_category成员类型是std::output_iterator_tag的别名)。

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

当然,这仅对没有执行策略的重载有效,在这种情况下,可能不使用std::back_inserter_iterator(这不是转发迭代器)。


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