Search code examples
c++templateslambdavariadic-templates

Lambda to compose lambdas


I tried to write a function in C++ which can compose a variable amount of lambdas. My first attempt kind of works (even though I suspect it isn't perfect)

template <typename F, typename G> auto compose(F f, G g) {
  return
      [f, g](auto &&...xs) { return g(f(std::forward<decltype(xs)>(xs)...)); };
}

template <typename F, typename G, typename... Fs>
auto pipe(F f, G g, Fs... fs) {
  if constexpr (sizeof...(fs) > 0) {
    auto fg = compose(f, g);
    return pipe(fg, fs...);
  } else {
    return compose(f, g);
  }
}

int main() {

  auto add_x = [](const auto &x) {
    return [x](auto y) {
      std::cout << "+" << x << std::endl;
      return y + x;
    };
  };

  auto to_str = [](const auto &s) {
    std::cout << "to_str" << std::endl;
    return std::string("String:") + std::to_string(s);
  };

  auto add_1 = add_x(1);
  auto add_2 = add_x(2);
  auto add_3 = add_x(3);

  auto piped = pipe(add_1, add_2, add_3, to_str);

  auto x = piped(3);

  std::cout << x << std::endl;
}

However, I'd like to have the pipe function itself to be a lambda function. This is kind of hard however, since, as I understood it, lambdas can't capture themselves. This makes simply "lambdafying" my template function problematic. Does anyone have an alternative approach or an idea how to get similar results with a lambda function?


Solution

  • You can use the y combinator to make a recursive lambda.

    template<class Fun>
    class y_combinator_result {
        Fun fun_;
    public:
        template<class T>
        explicit y_combinator_result(T &&fun): fun_(std::forward<T>(fun)) {}
    
        template<class ...Args>
        decltype(auto) operator()(Args &&...args) {
            return fun_(std::ref(*this), std::forward<Args>(args)...);
        }
    };
    
    template<class Fun>
    decltype(auto) y_combinator(Fun &&fun) {
        return y_combinator_result<std::decay_t<Fun>>(std::forward<Fun>(fun));
    }
    
    template <typename F, typename G> auto compose(F f, G g) {
      return
          [f, g](auto &&...xs) { return g(f(std::forward<decltype(xs)>(xs)...)); };
    }
    
    auto pipe = y_combinator([](auto self, auto f, auto g, auto... fs){
      if constexpr (sizeof...(fs) > 0) {
        auto fg = compose(f, g);
        return self(fg, fs...);
      } else {
        return compose(f, g);
      }
    });
    

    See it live