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

前端 未结 4 1355
北恋
北恋 2021-02-18 13:56

Cppreference has this example code for std::transform:

std::vector ordinals;
std::transform(s.begin(), s.end(), std::back_inserter(ordinals),
         


        
相关标签:
4条回答
  • 2021-02-18 14:36

    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.

    0 讨论(0)
  • 2021-02-18 14:37

    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!)

    0 讨论(0)
  • 2021-02-18 14:41

    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").

    0 讨论(0)
  • 2021-02-18 14:42

    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.

    0 讨论(0)
提交回复
热议问题