首页 > 解决方案 > C++ 最佳实践:通过 const 引用或转发引用(又名通用引用)将仅使用(未存储)的 lambda 参数传递给函数

问题描述

将 lambda 函数作为参数传递给仅使用(但不存储或转发)lambda 函数的函数的最佳(即最高效、最通用)的方法是什么?

选项 1:通过 const-reference 传递它是要走的路吗?

template <typename F>
void just_invoke_func(const F& func) {
    int i = 1;
    func(i);
}

选项2:还是将其作为转发参考(通用参考)传递更好?

template <typename F>
void just_invoke_func(F&& func) {
    int i = 1;
    func(i);
}

后者比前者有什么优势吗?
其中一种解决方案是否有任何危害?

编辑#1:

选项 3:正如Yakk - Adam Nevraumont所指出的,mutablelambdas 不适用于选项 1。那么,另一个采用非常量左值引用的版本呢:

template <typename F>
void just_invoke_func(F& func) {
    int i = 1;
    func(i);
}

问题是:选项 2 比选项 3 有什么优势吗?

编辑#2:

选项 4:已经提出了另一个按值获取 lambda 的版本。

template <typename F>
void just_invoke_func(F func) {
    int i = 1;
    func(i);
}

标签: c++c++17

解决方案


mutable带有关键字的Lambda有一个非常量operator(),并且在第一种情况下无法编译。

我认为没有理由阻止可变 lambda。

右值 lambda 在非常量左值参考案例中无法编译。

老实说,考虑按价值取 lambda。如果调用者想要非本地状态,他们可以通过引用捕获,甚至传入std::ref/std::cref包装的 lambdas(如果您正在执行不可尾调用优化的递归调用,右值引用可能是明智的)。

template<class F>
void do_something1( F&& f );
template<class F>
void do_something2( F f );

do_something1(lambda)可以通过调用(如果不可变)和(如果可变)来模拟do_something2(std::cref(lambda))调用do_something2(std::ref(lambda))

仅当 lambda 既是可变的又是右值时,这不起作用;9/10 次是一个错误(状态已被处理的可变 lambda 是不确定的),在剩余的 1/10 次中,您可以解决它。

另一方面,你不能轻易模仿do_something2do_something1

的优点do_something2是编译器在编写 的主体时do_something2,可以准确地知道lambda 的所有状态在哪里。

[x = 0](int& y)mutable{y=++x;}

如果此 lambda 是按值传递的,则编译器在本地知道谁可以访问x. 如果这个 lambda 是按引用传递的,编译器必须理解调用上下文和它调用的所有代码,以便知道其他人是否x在它自己的代码中的任何两个表达式之间进行了修改。

编译器通常可以比外部引用更好地优化值。

至于复制的成本,lambda 的移动通常并不昂贵。如果它们是[&]-capture,它们只是状态方面的一堆引用。如果它们是价值捕获的,它们的状态很少是巨大且难以移动的。

在这些情况下,std::ref或者std::cref无论如何都会消除该成本。

(传递给 lambda,所以函数永远不需要知道它被传递了std::ref) 。operator()std::ref(lambda)


推荐阅读