为什么 std::find_if(first, last, p) 不通过引用获取谓词?

Why does std::find_if(first, last, p) not take predicate by reference?

本文关键字:获取 谓词 引用 last find std if 为什么 first      更新时间:2023-10-16

我正在查看 cppreference.com 上std::find_if的各种签名,我注意到采用谓词函数的风格似乎通过值接受它:

template< class InputIt, class UnaryPredicate >
InputIt find_if( InputIt first, InputIt last,
UnaryPredicate p );

如果我理解正确,具有捕获变量的 lambda 为其数据的引用或副本分配存储,因此"按值传递"可能意味着为调用复制捕获数据的副本。

另一方面,对于函数指针和其他可直接寻址的东西,如果直接传递函数指针,而不是通过引用到指针(指针到指针),性能应该更好。

首先,这是正确的吗?上面的UnaryPredicate是按值参数吗?

第二,我对传递 lambda 的理解是否正确?

第三,在这种情况下,是否有理由按值传递而不是按引用传递?更重要的是,难道没有一些足够模棱两可的语法(你好,通用参考)可以让编译器做任何它想做的事情来获得最大的性能吗?

上面的 UnaryPredicate 会是一个按值参数吗?

是的,这就是它在函数参数列表中所说的。它接受推导值类型。

除此之外,lambda 表达式是 prvalue。这意味着,使用 c++17 的保证复制省略,该p直接从lambda 表达式初始化。将闭包或捕获的对象传递到函数中时,不会创建闭包或捕获对象的额外副本(但是,该函数可能会在内部创建更多副本,尽管这并不常见)。

如果谓词是通过引用传递的,则需要具体化临时对象。因此,对于 lambda 表达式,通过引用传递的开关不会获得任何好处。

如果您有其他类型的谓词,这些谓词可以扩展复制,那么您可以将std::reference_wrapper传递给该谓词对象,以获得便宜的"句柄"。包装器的operator()将做正确的事情。

这个定义主要是历史性的,但现在用传递值来做它真的不是问题。


为了详细说明为什么引用语义会很糟糕,让我们尝试通过这些年来。简单的左值引用是行不通的,因为现在我们不支持绑定到右值。常量左值引用也不起作用,因为现在我们要求谓词不修改任何内部状态,为了什么?

所以在 c++11 之前,我们真的没有其他选择。按值传递比引用更好。根据新标准,我们可能会修改我们的方法。为了支持右值,我们可以添加一个右值引用重载。但这是一种冗余练习,因为它不需要做任何不同的事情。

通过传递一个值,调用者可以选择如何创建它,对于 prvalues,在 c++17 中,它实际上是免费的。如果调用方愿意,他们可以显式提供引用语义。所以什么都没有丢失,我认为在简单使用和 API 设计方面收获了很多。

实际上有多种原因:

  1. 您始终可以将推导的值参数转换为使用引用语义,但不能使用反之亦然:只需传递std::ref(x)而不是xstd::reference_wrapper<T>并不完全等同于传递引用,但特别是对于函数对象,它执行正确的操作。也就是说,按值传递泛型参数是更通用的方法。

  2. 按引用传递(T&)不适用于临时或const对象,T const&不适用于非const&,即唯一的选择是T&&(转发引用),它在C++11之前不存在,算法接口自C++98引入以来没有改变。

  3. 值参数可以复制省略,这与任何类型的引用参数(包括转发引用)不同。