Cppreference has this example code for std::transform:
std::vector ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
I believe the transformation is guaranteed to be processed in-order. std::back_inserter_iterator
is an output iterator (its iterator_category
member type is an alias for std::output_iterator_tag
) according to [back.insert.iterator].
Consequently, std::transform
has no other option on how to proceed to next iteration than to call member operator++
on the result
parameter.
Of course, this is valid only for overloads without execution policy, where std::back_inserter_iterator
may not be used (it's not a forwarding iterator).
BTW, I wouldn't argument with quotes from cppreference. The statements there are often imprecise or simplified. In cases as these, it's better to look at the C++ Standard. Where, regarding to std::transform
, there is no quote about order of operations.
So the thing I missed is that the parallel versions take LegacyForwardIterators, not LegacyOutputIterator
. A LegacyForwardIterator
can be incremented without invalidating copies of it, so it is easy to use this to implement an out-of-order parallel std::transform
.
I think the non-parallel versions of std::transform
must be executed in-order. Either cppreference is wrong about it, or possibly the standard just leaves this requirement implicit because there is no other way to implement it. (Shotgun not wading through the standard to find out!)
1) The output iterator requirements in the standard are completely broken. See LWG2035.
2) If you use a purely output iterator and a purely input source range, then there's little else the algorithm can do in practice; it has no choice but to write in order. (However, a hypothetical implementation can choose to special-case its own types, like std::back_insert_iterator<std::vector<size_t>>
; I don't see why any implementation would want to do it here, but it is permitted to do so.)
3) Nothing in the standard guarantees that transform
applies the transformations in-order. We are looking at an implementation detail.
That std::transform
requires only output iterators does not mean it cannot detect higher iterator strengths and reorder the operations in such cases. Indeed, algorithms dispatch on iterator strength all the time, and they have special handling for special iterator types (like pointers or vector iterators) all the time.
When the standard wants to guarantee a particular order, it knows how to say it (see std::copy
's "starting from first
and proceeding to last
").
From n4385:
§25.6.4 Transform:
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);
Returns: back_insert_iterator(x).
§23.5.2.1 Class template back_insert_iterator
using iterator_category = output_iterator_tag;
So std::back_inserter
can't be used with parallel versions of std::transform
. The versions that support output iterators read from their source with input iterators. Since input iterators can only be pre- and post-incremented (§23.3.5.2 Input iterators) and there is only sequential (i.e. non-parallel) execution, order must be preserved between them and the output iterator.