Reputation: 45665
When I assign a lambda to an explicitly typed variable (for example when it is recursive, to capture the function in itself), I use std::function
.
Consider this silly "bit counting" function as an example:
std::function<int(int)> f;
f = [&f](int x){ return x ? f(x/2)+1 : 0; };
What about the case when we use an auto parameter to generalize x
, as introduced in C++14 generic lambda?
std::function<int(???)> f;
f = [&f](auto x){ return x ? f(x/2)+1 : 0; };
Obviously, I can't place auto
in the function
type parameters.
Is there a possibility to define a functor class generically enough to cover the exact case above, but still using lambda for the function definition?
(Don't over-generalize this, only accept a single auto parameter and hard-code the return value.) The use case would be for the scenario like above: capturing the function in itself by reference for recursive calls.
Upvotes: 6
Views: 1277
Reputation: 275310
Here is a quick y-combinator based recursive engine:
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)};
}
now you can do:
auto f = recurse( [](auto&& f, auto x){ return x ? f(x/2)+1 : 0; } );
and you get a recursive lambda that doesn't have a &
capture (which limits its use to the current scope).
Capturing a std::function
by reference means your lambda's lifetime is the current scope, and every recursive call requires going over type erasure (blocking any possible optimization, like tail-recursion, over the recursive call). The same holds true of other similar solutions.
The use of recursive_t
is required rather than using a lambda, because a lambda cannot name itself within itself.
A lambda based version is somewhat simpler in implementation. Note that you'd need a different type function for mutable and immutable lambdas:
template<class F>
auto recurse( F&& f ) {
return [f=std::forward<F>(f)](auto&&...args){
return f(f, decltype(args)(args)...);
};
};
The recursive_t
works like:
auto fib = recurse( [](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); } );
the lambda version works like:
auto fib = recurse( [](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); } );
which I, personally, find more awkward.
It is also harder to describe the type of recurse
. For the recursive_t
version, recurse
is of type:
((A->B)->A->B)->(A->B)
which is awkward, but a finite type.
The lambda version is trickier. The type of the function argument to recursive
is of type:
F:= F->A->B
which is annoyingly infinite, and then recurse
is of type
F->A->(A->B)
which inherits the infinity.
Anyhow, the recurse
return value can then be stored in a mundane std::function
, or not stored in any type-erased container.
Upvotes: 3
Reputation: 103693
You can create a lambda that calls itself by passing it to itself as a parameter:
auto f = [](auto self, auto x) -> int {
return x ? self(self, x / 2) + 1 : 0;
};
std::cout << f(f, 10);
You can then capture that lambda in another lambda, so you don't have to worry about passing it to itself:
auto f2 = [&f](auto x) {
return f(f, x);
};
std::cout << f2(10);
Upvotes: 4