不带Y Combinator的递归lambda回调
Recursive lambda callbacks without Y Combinator
我想创建一个回调,递归地返回自己作为回调。
建议的递归方法是让函数有一个对自身的引用:
std::function<void (int)> recursive_function = [&] (int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
recursive_function(recurse - 1);
}
};
当你从函数返回它时,它就会失败:
#include <functional>
#include <iostream>
volatile bool no_optimize = true;
std::function<void (int)> get_recursive_function() {
std::function<void (int)> recursive_function = [&] (int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
recursive_function(recurse - 1);
}
};
if (no_optimize) {
return recursive_function;
}
return [] (int) {};
}
int main(int, char **) {
get_recursive_function()(10);
}
在输出10
后,由于引用无效而产生分段错误。
我该怎么做?我已经成功地使用了我认为是一个Y Combinator(我将作为答案发布),但它非常令人困惑。有没有更好的办法?
其他尝试
我尝试了一个无聊的方法,把它包装在另一层回调中:
#include <functional>
#include <iostream>
#include <memory>
volatile bool no_optimize = true;
std::function<void (int)> get_recursive_function() {
// Closure to allow self-reference
auto recursive_function = [] (int recurse) {
// Actual function that does the work.
std::function<void (int)> function = [&] (int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
function(recurse - 1);
}
};
function(recurse);
};
if (no_optimize) {
return recursive_function;
}
return [] (int) {};
}
int main(int, char **) {
get_recursive_function()(10);
}
,但这在实际场景中失败,其中函数被延迟并由外部循环调用:
#include <functional>
#include <iostream>
#include <memory>
#include <queue>
volatile bool no_optimize = true;
std::queue<std::function<void (void)>> callbacks;
std::function<void (int)> get_recursive_function() {
// Closure to allow self-reference
auto recursive_function = [] (int recurse) {
// Actual function that does the work.
std::function<void (int)> function = [&] (int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
callbacks.push(std::bind(function, recurse - 1));
}
};
function(recurse);
};
if (no_optimize) {
return recursive_function;
}
return [] (int) {};
}
int main(int, char **) {
callbacks.push(std::bind(get_recursive_function(), 10));
while (!callbacks.empty()) {
callbacks.front()();
callbacks.pop();
}
}
给出10
,然后是9
,最后是分割故障。
正如您正确指出的那样,有一个来自lambda捕获[&]
的无效引用。
你的返回值是各种类型的函子,所以我假设返回值的确切类型并不重要,只需要它表现得像一个函数,即可调用。
如果recursive_function
被struct
或class
封装,则可以将调用操作符映射到recursive_function
成员。捕获this
变量时出现了一个问题。它将在创建时与this
一起被捕获,所以如果对象被复制了一点,原始的this
可能不再有效。因此,可以在执行时将适当的this
传递给函数(这个this
问题可能不是问题,但它在很大程度上取决于何时以及如何调用函数)。
#include <functional>
#include <iostream>
volatile bool no_optimize = true;
struct recursive {
std::function<void (recursive*, int)> recursive_function = [] (recursive* me, int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
me->recursive_function(me, recurse - 1);
}
};
void operator()(int n)
{
if (no_optimize) {
recursive_function(this, n);
}
}
};
recursive get_recursive_function() {
return recursive();
}
int main(int, char **) {
get_recursive_function()(10);
}
或者,如果recursive_function
可以是static
,那么在原始代码示例中声明它也可能对您有用。
我想为上面的答案添加一些一般性,即使其成为模板;
#include <functional>
#include <iostream>
volatile bool no_optimize = true;
template <typename Signature>
struct recursive;
template <typename R, typename... Args>
struct recursive<R (Args...)> {
std::function<R (recursive const&, Args... args)> recursive_function;
recursive() = default;
recursive(decltype(recursive_function) const& func) : recursive_function(func)
{
}
template <typename... T>
R operator()(T&&... args) const
{
return recursive_function(*this, std::forward<Args>(args)...);
}
};
recursive<void (int)> get_recursive_function()
{
using result_type = recursive<void (int)>;
if (!no_optimize) {
return result_type();
}
result_type result ([](result_type const& me, int a) {
std::cout << a << std::endl;
if (a > 0) {
me(a - 1);
}
});
return result;
}
int main(int, char **) {
get_recursive_function()(10);
}
这是如何工作的?基本上,它将递归从函数内部(即调用自身)移动到对象(即对象本身的函数操作符)以实现递归。在get_recursive_function
中,结果类型recursive<void (int)>
被用作递归函数的第一个参数。它是const&
,因为我已经将operator()
实现为const
,符合大多数标准算法和lambda函数的默认值。它确实需要函数实现者的一些"合作"(即使用me
参数;它本身是*this
)来让递归工作,但是为了这个代价,你得到了一个不依赖于堆栈引用的递归lambda。
编程中的所有问题都可以用另一层间接来解决,除非间接层太多。
我的目标是创建一个类型recursive<void(int)>
,使您可以轻松地创建递归lambda。要做到这一点,需要传入一个签名为void(recursive<void(int)>, int)
的lambda——第一个参数是为了进行递归调用而调用的。
然后我把它打个结,使它成为一个签名为void(int)
的完全递归函数。
这是我的recursive<Signature>
的实现:
template<class Sig>
struct recursive;
template<class R, class... As>
struct recursive< R(As...) > {
using base_type = std::function<R(recursive, As...)>;
private:
std::shared_ptr< base_type > base;
public:
template<typename...Ts>
auto operator()(Ts&&... ts) const
-> typename std::result_of< base_type( recursive, Ts... ) >::type
{
return (*base)(*this, std::forward<Ts>(ts)...);
}
recursive(recursive const&)=default;
recursive(recursive&&)=default;
recursive& operator=(recursive const&)=default;
recursive& operator=(recursive &&)=default;
recursive() = default;
template<typename L, typename=typename std::result_of< L(recursive, As...) >::type>
explicit recursive( L&& f ):
base( std::make_shared<base_type>(std::forward<L>(f)))
{}
explicit operator bool() const { return base && *base; }
};
无可否认,这相当复杂。我做了很多事情来提高效率,这样一个完美的转发。与std::function
不同的是,它还会仔细检查您传递给它的lambda的类型是否与它想要的签名匹配。
我相信,但还没有确认,我做了一个友好的lambdas签名void(auto&&,int)
。有人知道完全兼容的c++ y在线编译器吗?
以上只是样板。重要的是它在使用点的样子:
std::function<void (int)> get_recursive_function() {
auto f =
[] (recursive<void(int)> self, int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
self(recurse - 1);
}
};
return recursive< void(int) >( f );
};
这里我们使用流行的auto f = lambda
语法。不需要直接存储在std::function
.
然后,我们显式地将其强制转换为recursive<void(int)>
,这将其捆绑在一起,并从前面删除了f
的recursive<void(int)>
参数,并暴露了签名void(int)
。
这确实要求您的lambda将recursive<void(int)> self
作为其第一个参数,并通过它进行递归,但这似乎并不苛刻。如果我把写对了,它可能与auto&& self
作为第一个参数一起工作,但我不确定。
recursive<?>
自然适用于任何签名。
生活例子
并且,在外循环中延迟调用仍然有效。请注意,我去掉了这个全局变量(它可以作为一个全局变量使用,只是保留它感觉很脏)。
在c++ 1y中,我们可以消除类型擦除&上面看到的shared_ptr
开销(recursive
对象持有shared_ptr<function<?>>
)。您必须提供返回值,因为我无法获得result_of
来解开我的混乱:
struct wrap {};
template<class R, class F>
struct recursive {
using base_type = F;
private:
F base;
public:
template<class... Ts>
R operator()(Ts&&... ts) const
{
return (*base)(*this, std::forward<Ts>(ts)...);
}
recursive(recursive const&)=default;
recursive(recursive&&)=default;
recursive& operator=(recursive const&)=default;
recursive& operator=(recursive &&)=default;
recursive() = delete;
template<typename L>
recursive( wrap, L&& f ):
base( std::forward<L>(f) )
{}
};
template<class T>using decay_t = typename std::decay<T>::type;
template<class R, class F>
recursive<R, decay_t<F>> recurse( F&& f ) { return recursive<R, decay_t<F>>(wrap{}, std::forward<F>(f)); }
然后是get_recursive_function
的稍微不同的实现(我添加了一些状态):
std::function<void (int)> get_recursive_function(int amt) {
auto f =
[amt] (auto&& self, int count) {
std::cout << count << std::endl;
if (count > 0) {
self(count - amt);
}
};
return recurse<void>( std::move(f) );
};
int main() {
auto f = get_recursive_function(2);
f(10);
}
在get_recursive_function
的返回值中使用std::function
是可选的——您可以在c++ 1y中使用auto
。与完美的版本(lambda可以访问自己的operator()
)相比,仍然有一些开销,因为operator()
在调用self
时可能不知道它正在对同一对象进行递归调用。
在lambda的体中允许operator()( blah )
以允许对lambda的递归调用是很诱人的。它可能会破坏很少的代码。
我目前的解决方法是:
#include <functional>
#include <iostream>
#include <queue>
volatile bool no_optimize = true;
std::queue<std::function<void (void)>> callbacks;
(我认为这是一个Y Combinator,但我不确定)
std::function<void (int)> y_combinator(
std::function<void (std::function<void (int)>, int)> almost_recursive_function
) {
auto bound_almost_recursive_function = [almost_recursive_function] (int input) {
y_combinator(almost_recursive_function)(input);
};
return [almost_recursive_function, bound_almost_recursive_function] (int input) {
almost_recursive_function(bound_almost_recursive_function, input);
};
}
这是基函数;它不调用自己,而是调用传递给它的参数。这个参数应该是递归函数本身。
std::function<void (std::function<void (int)>, int)> get_almost_recursive_function() {
auto almost_recursive_function = (
[] (std::function<void (int)> bound_self, int recurse) {
std::cout << recurse << std::endl;
if (recurse > 0) {
callbacks.push(std::bind(bound_self, recurse - 1));
}
}
);
if (no_optimize) {
return almost_recursive_function;
}
return [] (std::function<void (int)>, int) {};
}
因此,可以通过将组合子应用于几乎递归的函数来得到所需的函数。
std::function<void (int)> get_recursive_function() {
return y_combinator(get_almost_recursive_function());
}
运行main
时,输出10
, 9
,…, 0
, as desired
int main(int, char **) {
callbacks.push(std::bind(get_recursive_function(), 10));
while (!callbacks.empty()) {
callbacks.front()();
callbacks.pop();
}
}
既然您已经处理了在各处都增加了一些开销的std::function
,那么您可以添加内存持久性,它只会在调用站点使用unique_ptr
:
std::unique_ptr<std::function<void (int)>> CreateRecursiveFunction() {
auto result = std::make_unique<std::function<void (int)>>();
auto ptr = result.get();
*result = [ptr] (int recurse) { // c++1y can also capture a reference to the std::function directly [&func = *result]
std::cout << recurse << std::endl;
if (recurse > 0) {
(*ptr)(recurse - 1); // with c++1y func( recurse - 1 )
}
};
return result;
}
- FLTK:按下哪个按钮 - 将数字传递给按钮的回调 (lambda)
- 使用带有闭包的 lambda 的回调
- 通过实用程序 fn 将捕获的 lambda 传递给 C 样式回调 - 错误
- C++模板函数中,指定回调函子/lambda 的参数类型,同时仍允许内联?
- 如何成功地将函数对象(或lambda)传递给trackbar回调的第二个参数(void*)
- ROS:在nodehandle.subscribe中使用lambda作为回调
- 如何定义与将 Lambda 与捕获作为回调一起使用兼容的函数指针
- lambda回调中Android Cocos2D-X应用程序上的SEG故障
- 在库中的 C++17 中强制内联回调 (lambda)
- 如何使用C lambda将成员功能指针转换为普通功能指针,以用作回调
- std::函数,带有 SDL 事件回调的 lambda 错误
- 将lambda传递到lambda回调参数
- C lambda/回调弹出窗口
- C lambda回调为触发事件
- 允许 lambda/回调函数的多个签名作为模板参数
- 读取 Lambda 回调后的访问冲突
- 如何避免在调用异步函数进行 lambda 回调时出现额外的副本
- C++ VS 2013 中的 11 lambda 回调无法编译
- 不带Y Combinator的递归lambda回调
- 透明地配置std::function / lambda回调