C ++递归Variadic Lambda

时间:2018-03-26 15:20:05

标签: c++ templates recursion lambda c++14

在C ++中,我们可以有一个递归的可变参数模板函数。例如:

template<typename ...H>
void g(){}
template<typename H, typename ...T>
void g(H h,T ...t){
    std::cout << h << "\t";
    g<T...>(t...);
}

然而,使用lambdas似乎无法做到这一点。我的两个主要关注点是:

  • 如何建立基础案例。
  • 如何使lambda具有递归性,同时具有可变性。

我知道我可以有递归的lambdas但是看不到让它变成可变的方法。 这种类型的功能是否仅适用于更高级别的语言,例如Javascript?

编辑: 到目前为止,这是我提出的:

template<typename C,typename H, typename ...T>
std::function<void(C,H,T...)> f=[](auto&&  self,H h,T ...t){
    std::cout << h << "\t";
    if(sizeof...(t)>0)
        self(self,t...);
};

这里的第一个参数是lambda本身。 然而,主要的问题是,为了调用这个方法,我将定义类型C,我不确定如何做(或者即使它可能)。

编辑: 更简单的方法是:

auto f = [] (auto&&  self, auto&& h,auto&&... t) {
    std::cout << sizeof...(t) << "\n";
    if( sizeof...(t)>0 ){
        self(self,1);
    }
};

int main()
{
    f(f,1,2,3,4,5,6);
    return 0;
}

但是会出现以下错误:

main.cpp:55:13: error: use of ' [with auto:1 = &; auto:2 = int; auto:3 = {}]' before deduction of 'auto'
     self(self,1);
         ^

2 个答案:

答案 0 :(得分:5)

C ++ 17使用Constexpr if解决了这个问题:

#include <iostream>

auto f = [](auto&&... t){
    auto f_impl = [](auto& self, auto&& h, auto&&... t) {
      std::cout << sizeof...(t) << "\n";
      if constexpr ( sizeof...(t)>0 ){
        self(self,t...);
      }
    };
    return f_impl(f_impl, t...);
};

int main() {
    f(1,2,3,4,5,6);
}

我还冒昧地将第一个参数(self)包装在&#34;父母&#34;拉姆达。

使用if(sizeof...(t))作为保护的问题是,即使您不在运行时使用不正确的参数调用lambda,编译器仍需要使用{{1}编译表达式self(self, t...) },失败了。

sizeof...(t)==0通过在编译时进行此检查来解决此问题,甚至在检查产生constexpr if时甚至不编译块。在C ++ 17之前,条件编译语义(不包括宏)只能使用SFINAE或模板专业化来实现,这两者都不能仅使用lambdas来完成。

答案 1 :(得分:2)

我讨厌帮手,我认为使用帮手是一种误导性的设计。也就是说,如果不使用小帮手,我无法找到解决方案。

这里遇到的障碍如下:

  • 您不能在其自己的初始化表达式中引用用auto声明的变量

    这可以通过使用内部lambda将函数作为参数传递给自身来解决,以提供干净的公共接口

  • 您不能将if( sizeof... )用作&#34; base-case&#34;的保护(或任何其他&#34;运行时&#34;机制)。因为你仍然会调用一个函数,该函数不能接受那个if块中没有编译的参数数量

    这是通过&#34;重载lambda&#34;来解决的。并实际定义了所有需要的函数重载(即&#34;基本情况&#34;也)

  • 你不能简单地&#34;&#34;一旦定义了lambda就会重载

    这是通过使用&#34; small&#34;帮助代码

总而言之,一个解决方案可能就是这样:

#include <iostream>

/////////// Helper

template<class F, class... Fs>
struct overloaded : F, overloaded<Fs...>
{
  using F::operator();
  using overloaded<Fs...>::operator();

  overloaded(F&& f, Fs&&... fs)
    : F(std::move(f))
    , overloaded<Fs...>(std::move(fs)...)
  {}
};

template<class F>
struct overloaded<F> : F
{
  using F::operator();

  overloaded(F&& f)
    : F(std::move(f))
  {}
};

template<class... Ts>
overloaded<Ts...> overload(Ts&&...lambdas)
{ return overloaded<Ts...>{std::move(lambdas)...}; }

///////// Recursive Variadic Lambda

int     main(void)
{
  auto lambda = [](auto... args)
    {
      auto lambda_impl = overload(
        [](auto self)
        {
        },
        [] (auto self, auto first, auto...rest)
        {
          std::cout << first << std::endl;
          self(self, rest...);
        });

      lambda_impl(lambda_impl, args...);
    };

  lambda(4, "lol", 8.3);
}