是否有std::函数类型或类似于带有auto参数的lambda

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

本文关键字:auto 参数 lambda std 函数 类型 是否 类似于      更新时间:2023-10-16

当我将lambda赋值给显式类型变量时(例如,当它是递归的时,为了捕获函数本身),我使用std::function

以这个愚蠢的"位计数"函数为例:

std::function<int(int)> f;
f = [&f](int x){ return x ? f(x/2)+1 : 0; };

如果我们使用auto参数泛化x,就像c++ 14 generic lambda中介绍的那样呢?

std::function<int(???)> f;
f = [&f](auto x){ return x ? f(x/2)+1 : 0; };

显然,我不能把auto放在function类型参数中。

是否有可能定义一个泛型的函数类,足以覆盖上面的确切情况,但仍然使用lambda函数定义?

(不要过于一般化,只接受一个auto参数并硬编码返回值。)该用例将适用于上面的场景:通过引用捕获函数本身以进行递归调用。

您可以通过将其作为参数传递给自身来创建调用自身的lambda:

auto f = [](auto self, auto x) -> int {
    return x ? self(self, x / 2) + 1 : 0;
};
std::cout << f(f, 10);

然后你可以在另一个lambda中捕获这个lambda,所以你不必担心将它传递给它自己:

auto f2 = [&f](auto x) {
    return f(f, x);
};
std::cout << f2(10);

这是一个快速的基于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; } );

,你得到一个递归lambda,它没有&捕获(这限制了它在当前范围内的使用)。

通过引用捕获std::function意味着lambda的生命周期是当前作用域,并且每个递归调用都需要越过类型擦除(阻塞任何可能的优化,例如尾递归,越过递归调用)。其他类似的解决方案也是如此。

需要使用recursive_t而不是使用lambda,因为lambda不能在自己内部命名自己。

生活例子。

基于lambda的版本在实现上稍微简单一些。注意,对于可变的和不可变的lambdas,您需要一个不同的类型函数:

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

lambda版本的工作方式如下:

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)

有点别扭,但这是一个有限类型。

lambda版本更棘手。recursive的函数实参类型为:

F:= F->A->B

是一个烦人的无穷大,然后recurse的类型是

F->A->(A->B)

继承了无穷大

无论如何,recurse返回值可以存储在普通的std::function中,或者不存储在任何类型擦除的容器中。