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


20

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_op或的顺序应用binary_op。要将功能按顺序应用于序列或将功能修改序列的元素,请使用std::for_each

大概是为了允许并行实现。但是,第三个参数std::transform是a LegacyOutputIterator,它具有以下条件++r

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

因此在我看来,输出的分配必须按顺序进行。它们是否仅表示的应用程序unary_op可能会乱序,并存储到一个临时位置,但按顺序复制到输出中?这听起来不像您想做的事情。

大多数C ++库实际上尚未实现并行执行程序,但Microsoft已实现。我很确定是相关的代码,并且我认为它调用populate()函数来将迭代器记录到输出的大块中,这肯定不是一件有效的事情,因为 LegacyOutputIterator可以通过递增其副本来使其无效。

我想念什么?


在一个简单的测试godbolt表明这是一个问题。使用C ++ 20和transform版本决定是否使用并行处理。该transform大型向量失败。
Croolman

6
@Croolman您的代码是错误的,因为您要向后插入s,从而使迭代器无效。
丹尼尔·兰格

@DanielsaysreinstateMonica噢,炸肉排是正确的。正在对其进行调整,并将其置于无效状态。我回想一下。
Croolman

如果使用std::transform严格策略,则需要随机访问迭代器,back_inserter不能满足要求。IMO引用的零件文档涉及该情况。说明文档中的示例使用std::back_inserter
Marek R

@Croolman决定自动使用并行性吗?
curiousguy19年

Answers:


9

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

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

3)标准中没有任何内容可以按transform顺序应用转换。我们正在研究实现细节。

std::transform仅需要输出迭代器并不意味着它不能在这种情况下,检测更高的迭代器的优势和重新排序操作。事实上,调度算法对迭代器强度所有的时间,他们有特殊的迭代器类型进行特殊处理(如指针或矢量迭代器)所有的时间

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


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


2
请注意,来自C ++标准的这些定义并不能避免实现以提供为其他类型的迭代器选择的特殊算法版本。例如,std::advance只有一个定义接受输入迭代器,但libstdc ++为双向迭代器random-access-iterator提供了附加版本。然后根据传递的迭代器的类型执行特定版本。
丹尼尔·兰格

我认为您的评论不正确- ForwardIterator并不意味着您必须按部就班。但是您已经强调了我错过的事情-对于它们ForwardIterator没有使用的并行版本OutputIterator
Timmmm

1
嗯,是的,我想我们同意。
Timmmm

1
可以通过添加一些词来解释它的实际含义,从而使此答案受益。
巴里

1
@Barry添加了一些单词,任何和所有反馈非常感谢。
保罗·埃文斯

0

所以我错过的是并行版本采用LegacyForwardIterators,而不是LegacyOutputIteratorLegacyForwardIterator 可以增加A 而不会使它的副本无效,因此很容易使用它来实现无序的parallel std::transform

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


如果所有迭代器都足够强大,则转换的非并行版本可能会无序执行。在问题的示例中,它们不是,因此必须按顺序进行专门transform
卡莱斯(Caleth),

不,他们可能不会,因为LegacyOutputIterator强迫您按顺序使用它。
Timmmm

它可以针对std::back_insert_iterator<std::vector<T>>和进行不同的专业化处理std::vector<T>::iterator。首先必须是有秩序的。第二个没有这样的限制
Caleth

啊,等等,我明白你的意思了-如果您碰巧将a传递给LegacyForwardIterator非parallel transform,它可能会因其无序而专门化。好点子。
Timmmm

0

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

因此,std::transform没有其他选择,如何进行下一次迭代,而不是调用成员operator++result参数。

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


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


“ C ++标准。关于std :: transform的地方,没有关于操作顺序的引用。”由于未提及该顺序,因此它是否未指定?
HolyBlackCat

@HolyBlackCat显式未指定,但由输出迭代器强加。请注意,使用输出迭代器,一旦增加它,就不能取消引用任何先前的迭代器值。
丹尼尔·朗格19/12/12
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.