将 std::transform 与 std::back_inserter 一起使用是否有效?
Is it valid to use std::transform with std::back_inserter?
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
的第三个参数是一个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 输入迭代器(,并且只有顺序(即非并行(执行,因此必须在它们和输出迭代器之间保持顺序。
所以我错过的是并行版本需要LegacyForwardIterator
s,而不是LegacyOutputIterator
。LegacyForwardIterator
可以在不使其副本失效的情况下递增,因此很容易使用它来实现无序并行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
,没有关于操作顺序的引用。
- 检查 std::shared_ptr<> 的当前底层类型是否为 T
- 关于std::move的使用,是否有编译警告
- 通过网络、跨平台传递std::变体是否安全
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- std::vector::迭代器是否可以合法地作为指针
- MESI协议和std::atomic-它是否确保所有写入立即对其他线程可见?
- 是否将std::packaged_task添加到现有线程
- C++中是否存在 std::conditional 的懒惰等价物?
- 检查某些类型是否是模板类 std::optional 的实例化
- 如何检查两个 std::向量在小于 O(n) 的时间复杂度内是否相等
- 在C++中,使用带有 std::optional 参数的函数<T>来表示可选参数是否有意义?
- C++标准是否允许<double>在没有开销的情况下实现 std::可选
- '[](std::list& list)<int>{return std::move(list)}(list)' 是否保证将 'list' 留空?
- "std::list::splice(std::const_iterator pos, std::list&& other)"是否保证将"其他"留空?
- 为什么程序员同时使用 std::bad_alloc 和 std::exception.是否 std::例外 仅是不够的
- glibcxx STL 在实现 std::valarray::sum() 时是否不正确?
- 在 C++20 中是否不再允许在 std 中对程序定义类型的函数模板进行专用化?
- 将 std::map::emplace 与返回 shared_ptr 的函数一起使用是否正确?
- "using namespace std;"是否免于过多代码规则?
- GCC 或 Clang '-std=' 是否有任何"moving target"别名值,表示"use the latest standard"?