简体   繁体   中英

Is there a std::function type or similar for lambda with auto parameter?

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.

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);

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.

Live example .

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM