不带Y Combinator的递归lambda回调

Recursive lambda callbacks without Y Combinator

本文关键字:lambda 回调 递归 Combinator 不带      更新时间:2023-10-16

我想创建一个回调,递归地返回自己作为回调。

建议的递归方法是让函数有一个对自身的引用:

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_functionstructclass封装,则可以将调用操作符映射到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)>,这将其捆绑在一起,并从前面删除了frecursive<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;
}