为何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可拷贝构造,但它依然返回函数对象,主要有以下几个原因:
API延续性:保持和传统
std::for_each的行为一致,降低用户从旧API迁移到Ranges的学习成本。很多开发者已经习惯通过auto func = for_each(...)获取遍历后的状态化函数对象,Ranges版本保留这个行为,符合用户的使用习惯。高效支持移动语义:即使
Fun可拷贝,返回移动后的原对象比拷贝更高效。对于拷贝成本较高的函数对象,直接返回移动后的实例能避免不必要的性能开销。状态保留的实际需求:即便函数对象可拷贝,用户仍可能希望直接复用经过遍历修改的原对象。比如一个自定义的统计器,遍历过程中已经累积了数据,返回它可以直接使用,无需提前拷贝。
Ranges API的统一范式:Ranges系列算法通常返回包含迭代器和相关对象的结果结构体(如
ranges::copy_result、ranges::find_result),这是一种统一的设计风格,让所有算法的返回值格式可预测,提升API的一致性。
内容的提问来源于stack exchange,提问作者康桓瑋

