以下は、簡単な y コンビネータ ベースの再帰エンジンです。
template<class F>
struct recursive_t {
F f;
// note Self must be an lvalue reference. Things get
// strange if it is an rvalue:
// invoke makes recursive ADL work a touch better.
template<class Self, class...Args>
friend auto invoke( Self& self, Args&&...args )
-> decltype( self.f( self, std::declval<Args>()... ) )
{
return self.f( self, std::forward<Args>(args)... );
}
// calculate return type using `invoke` above:
template<class Self, class...Args>
using R = decltype( invoke( std::declval<Self>(), std::declval<Args>()... ) );
template<class...Args>
R<recursive_t&, Args...> operator()(Args&&...args)
{
return invoke( *this, std::forward<Args>(args)... );
}
template<class...Args>
R<recursive_t const&, Args...> operator()(Args&&...args)const
{
return invoke( *this, std::forward<Args>(args)... );
}
};
template<class F>
recursive_t< std::decay_t<F> > recurse( F&& f )
{
return {std::forward<F>(f)};
}
今、あなたはできる:
auto f = recurse( [](auto&& f, auto x){ return x ? f(x/2)+1 : 0; } );
そして、キャプチャを持たない再帰ラムダを取得します&
(これにより、その使用が現在のスコープに制限されます)。
参照によってa をキャプチャするというstd::function
ことは、ラムダの有効期間が現在のスコープであることを意味し、すべての再帰呼び出しで型消去を行う必要があります (再帰呼び出しで末尾再帰などの可能な最適化をブロックします)。同じことが他の同様のソリューションにも当てはまります。
recursive_t
ラムダはそれ自体で名前を付けることができないため、ラムダを使用するのではなく、 を使用する必要があります。
実例。
ラムダベースのバージョンは、実装がやや簡単です。変更可能なラムダと不変のラムダには、異なる型の関数が必要になることに注意してください。
template<class F>
auto recurse( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
return f(f, decltype(args)(args)...);
};
};
次のrecursive_t
ような作品:
auto fib = recurse( [](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); } );
ラムダ版は次のように機能します。
auto fib = recurse( [](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); } );
私は、個人的にはもっと厄介だと思います。
の型を記述するのも難しいですrecurse
。recursive_t
バージョンの場合、recurse
タイプは次のとおりです。
((A->B)->A->B)->(A->B)
これは厄介ですが、有限型です。
ラムダ版はよりトリッキーです。関数の引数recursive
の型は次の型です。
F:= F->A->B
これはいらいらするほど無限であり、次にrecurse
タイプです
F->A->(A->B)
無限を受け継ぐもの。
とにかく、recurse
戻り値は通常std::function
の に格納するか、型消去されたコンテナに格納しないことができます。