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

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

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

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

如果我理解正确的话,具有捕获变量的 lambda 会为其数据的引用或副本分配存储空间,因此 "pass-by-value" 可能意味着捕获数据的副本已被复制用于调用。

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

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

其次,我对传递 lambda 的理解正确吗?

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

Is the UnaryPredicate above going to be a by-value parameter?

是的,函数参数列表中就是这么写的。它接受推导出的值类型。

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

如果谓词通过引用传递,则需要具体化一个临时对象。因此,对于 lambda 表达式,切换为按引用传递没有任何好处。

如果您有其他类型的谓词,这些谓词的复制范围很广,那么您可以将 std::reference_wrapper 传递给该谓词对象,以便宜的 "handle" 传递给它。包装器的 operator() 会做正确的事情。

该定义主要 具有历史意义,但如今使用按值传递确实不是问题。


为了详细说明为什么引用语义会很糟糕,让我们试着回顾一下这些年。一个简单的左值引用是行不通的,因为现在我们不支持绑定到右值。 const 左值引用也不会做任何事情,因为现在我们要求谓词不修改任何内部状态,这是为了什么?

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

通过传递一个值,调用者可以选择如何创建它,对于纯右值,在 中几乎是免费的。如果调用者愿意,他们可以显式提供引用语义。所以什么都没有丢失,而且我认为在使用简单性和 API 设计方面收获很大。

其实有多种原因:

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

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

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