You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何ranges::for_each仍返回函数?虽已保证Fun可拷贝构造

为什么ranges::for_each在要求函数可拷贝构造的情况下仍返回函数对象?

先回顾传统std::for_each与并行版的设计逻辑

传统std::for_each返回函数对象的原因

传统std::for_each的签名如下:

template<class InputIterator, class Function>
constexpr Function for_each(InputIterator first, InputIterator last, Function f);

标准仅要求Function满足Cpp17MoveConstructible,无需支持拷贝构造。这种设计的核心价值是允许用户复用带有状态的函数对象——比如一个用于累加的计数器,遍历结束后可以从返回的函数对象中直接获取统计结果,不用额外传递引用或指针。

并行版for_each返回void的原因

并行版for_each的签名是:

template<class ExecutionPolicy, class ForwardIterator, class Function>
void for_each(ExecutionPolicy&& exec,
              ForwardIterator first, ForwardIterator last,
              Function f);

它要求Function满足Cpp17CopyConstructible,因为实现会为每个工作线程拷贝一份函数对象。此时返回原函数对象没有意义——原对象的状态不会被遍历修改(每个线程用的是拷贝),用户可以在调用前自行拷贝需要保留的版本,因此设计为返回void。

回到ranges::for_each的设计

ranges::for_each的签名返回包含迭代器和函数对象的结果结构体:

template<input_iterator I, sentinel_for<I> S, class Proj = identity,
         indirectly_unary_invocable<projected<I, Proj>> Fun>
constexpr ranges::for_each_result<I, Fun>
  ranges::for_each(I first, S last, Fun f, Proj proj = {});

尽管indirectly_unary_invocable约束隐含了Fun可拷贝构造,但它依然返回函数对象,主要有以下几个原因:

  1. API延续性:保持和传统std::for_each的行为一致,降低用户从旧API迁移到Ranges的学习成本。很多开发者已经习惯通过auto func = for_each(...)获取遍历后的状态化函数对象,Ranges版本保留这个行为,符合用户的使用习惯。

  2. 高效支持移动语义:即使Fun可拷贝,返回移动后的原对象比拷贝更高效。对于拷贝成本较高的函数对象,直接返回移动后的实例能避免不必要的性能开销。

  3. 状态保留的实际需求:即便函数对象可拷贝,用户仍可能希望直接复用经过遍历修改的原对象。比如一个自定义的统计器,遍历过程中已经累积了数据,返回它可以直接使用,无需提前拷贝。

  4. Ranges API的统一范式:Ranges系列算法通常返回包含迭代器和相关对象的结果结构体(如ranges::copy_result、ranges::find_result),这是一种统一的设计风格,让所有算法的返回值格式可预测,提升API的一致性。


内容的提问来源于stack exchange,提问作者康桓瑋

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 04:35:26