提问人:康桓瑋 提问时间:9/28/2023 最后编辑:Remy Lebeau康桓瑋 更新时间:9/29/2023 访问量:1602
为什么ranges::for_each返回函数?
Why does ranges::for_each return the function?
问:
作为标准的遗留返回函数只需要根据 [alg.foreach] 满足 Cpp17MoveConstructible:std::for_each
Function
template<class InputIterator, class Function> constexpr Function for_each(InputIterator first, InputIterator last, Function f);
前提条件:满足 Cpp17MoveConstructible 要求。
Function
[注:不需要满足Cpp17CopyConstructible的要求。
Function
这是合理的,因为用户可能希望在调用后重用该函数。
并行版本没有回报:for_each
template<class ExecutionPolicy, class ForwardIterator, class Function> void for_each(ExecutionPolicy&& exec, ForwardIterator first, ForwardIterator last, Function f);
前提条件:满足 Cpp17CopyConstructible 要求。
Function
这是因为该标准要求满足 Cpp17CopyConstructible,因此返回函数是不必要的,因为用户可以根据需要在调用端自由创建副本。Function
我注意到这也返回了函数: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 = {});
但是,函数签名已经需要满足,这已经保证它是可复制构造的。Fun
indirectly_unary_invocable
问题是,为什么 still 返回函数?这样做有什么意义?ranges::for_each
答:
它返回函子,因为这允许在当时使用有状态函子进行一些巧妙的技巧(我假设在 C++98 中)。你今天不经常看到这些,因为 lambda 通常更简单。
下面是一个示例:
#include <algorithm>
#include <iostream>
struct EvenCounter
{
int count;
EvenCounter() : count(0) {}
void operator()(int x)
{
if (x % 2 == 0)
count++;
}
};
int main()
{
int array[] = {1,2,3,4,5};
int num_even = std::for_each(array, array+5, EvenCounter()).count;
std::cout << num_even << '\n';
}
这是合理的,因为用户可能希望在调用后重用该函数。
我认为这里的逻辑是倒退的。该函数不需要可复制,因为没有理由复制它。for_each
如果你有一个不可复制(甚至不可移动)的函数,你可以通过引用 using 来传递它,以避免复制/移动,这样你就不会从将函数返回给你的算法中赢得任何东西。std::ref
C++ 中没有,但也没有移动语义,所以一开始就不能使用不可复制的函子。std::ref
for_each
评论
ranges::for_each
ranges::for_each(...).fun.count
这是一个简单的用例:
struct sum {
int total;
auto operator ()(const int x) { total += x ; }
};
int main() {
const auto v = std::vector{ 1, 2, 3, };
const auto [i, f] = std::ranges::for_each(v, sum());
std::cout << f.total;
}
编译器资源管理器
注意:当我写完这篇文章时,我注意到已经有一个类似的答案......
我目前正在寻找这个选择背后的实际理由,当我从信誉良好的来源得到答案时,我会更新它。
问题是,为什么 still 返回函数?这样做有什么意义?
ranges::for_each
这里的答案很简单:所有算法要么 (a) 返回与 (例如 , , 等) 相同的内容,要么 (b) 额外返回新高级的输入迭代器(例如 ),如果返回有用的话。从技术上讲,一些返回值的函数(如,再次)也可能从返回结束迭代器中受益,但这对人体工程学来说是一个很大的打击,所以这并没有发生。std::ranges::meow
std::meow
count
find
copy
count
值得注意的是,它们都 (a) 保留或 (b) 添加到现有返回类型中。
没有人愿意经历并实际更改算法的返回类型,并在 Ranges 已经完成的大量工作之上做出额外的设计决策。正如我们最喜欢的猫已经指出的那样,在 C++11 中,返回函数对象本身已经是不必要的了,因为如果你真的愿意,你可以通过简单地使用来完成有状态的事情 - 但弃用它并更改为返回似乎不值得,然后加倍地,它似乎不值得成为实际上在行为上与对应算法不同的†算法。std::for_each
std::ref
std::for_each
void
std::ranges::for_each
std::
†好吧,从技术上讲,这不是唯一的。 指定发生时发生的迭代器增量数。我碰巧在今年的 CppNow 演讲中谈到了这一点:take(5)。
std::ranges::copy
std::copy
评论
std::ranges::for_each
成为一种在行为上实际上与 std::
对应物不同的†算法似乎不值得。我想知道为什么他们对这种行为差异感到如此不情愿,在以概念形式设计类型特征对应物时,他们似乎没有这个问题(例如:)。std::assignable_from<std::string&, char>
评论
std::for_each