为什么ranges::for_each返回函数?

Why does ranges::for_each return the function?

提问人:康桓瑋 提问时间:9/28/2023 最后编辑:Remy Lebeau康桓瑋 更新时间:9/29/2023 访问量:1602

问:

作为标准的遗留返回函数只需要根据 [alg.foreach] 满足 Cpp17MoveConstructiblestd::for_eachFunction

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 = {});

但是,函数签名已经需要满足,这已经保证它是可复制构造的。Funindirectly_unary_invocable

问题是,为什么 still 返回函数?这样做有什么意义?ranges::for_each

C++ 律师 C++20 语言设计 标准范围

评论

1赞 Toby Speight 9/28/2023
有趣的问题。这可能只是为了让它更容易作为 的替代品。但这纯粹是猜测。std::for_each

答:

25赞 HolyBlackCat 9/28/2023 #1

它返回函子,因为这允许在当时使用有状态函子进行一些巧妙的技巧(我假设在 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::reffor_each

评论

0赞 康桓瑋 9/28/2023
感谢您的猜测。但是,我认为没有必要在现代 C++ 中特别允许这种诡计,尤其是对于更强大的 C++20 约束函数。请注意,这不仅会带来乐趣,而且拼写将在您的情况下进行,这更没有意义。ranges::for_eachranges::for_each(...).fun.count
0赞 HolyBlackCat 9/28/2023
@康桓瑋我猜有些人仍然喜欢这个把戏,或者至少委员会成员是这么认为的。
3赞 viraltaco_ 9/29/2023 #2

这是一个简单的用例:

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;
}

编译器资源管理器
注意:当我写完这篇文章时,我注意到已经有一个类似的答案......

我目前正在寻找这个选择背后的实际理由,当我从信誉良好的来源得到答案时,我会更新它。

12赞 Barry 9/29/2023 #3

问题是,为什么 still 返回函数?这样做有什么意义?ranges::for_each

这里的答案很简单:所有算法要么 (a) 返回与 (例如 , , 等) 相同的内容,要么 (b) 额外返回新高级的输入迭代器(例如 ),如果返回有用的话。从技术上讲,一些返回值的函数(如,再次)也可能从返回结束迭代器中受益,但这对人体工程学来说是一个很大的打击,所以这并没有发生。std::ranges::meowstd::meowcountfindcopycount

值得注意的是,它们都 (a) 保留或 (b) 添加到现有返回类型中。

没有人愿意经历并实际更改算法的返回类型,并在 Ranges 已经完成的大量工作之上做出额外的设计决策。正如我们最喜欢的猫已经指出的那样,在 C++11 中,返回函数对象本身已经是不必要的了,因为如果你真的愿意,你可以通过简单地使用来完成有状态的事情 - 但弃用它并更改为返回似乎不值得,然后加倍地,它似乎不值得成为实际上在行为上与对应算法不同的算法。std::for_eachstd::refstd::for_eachvoidstd::ranges::for_eachstd::


好吧,从技术上讲,这不是唯一的。 指定发生时发生的迭代器增量数。我碰巧在今年的 CppNow 演讲中谈到了这一点:take(5)。std::ranges::copystd::copy

评论

0赞 303 9/30/2023
“......然后加倍地,让 std::ranges::for_each 成为一种在行为上实际上与 std:: 对应物不同的†算法似乎不值得。我想知道为什么他们对这种行为差异感到如此不情愿,在以概念形式设计类型特征对应物时,他们似乎没有这个问题(例如:)。std::assignable_from<std::string&, char>
0赞 Barry 9/30/2023
@303这两件事没有关系吗?事实证明,人们可以对不同的问题做出不同的决定。