Is it possible in C++11 to combine functions into a new function?

痴心易碎 提交于 2021-02-20 18:51:54

问题


This is more a kind of theoretical question. Is it possible in C++11 to combine functions into a new function? For example :

auto f = [](int i){return i * 2;};
auto g = [](int i){return i + 10;};

So this works:

auto c = f(g(20)); // = 60

But I want an object that stores the combination, like

auto c = f(g);
std::cout << c(20) << std::endl; //prints 60

Edit: Additionally what i want to create is a function a, which you can give a function b and an int n, and which returns the n'th combination of the given function b. For example (not compilable)

template<typename T>
auto combine(T b, int i) -> decltype(T)
{
   if (i == 0)
      return b;

   return combine(b, i - 1);      
}

auto c = combine(f, 2); //c = f(f(f(int)))

回答1:


You can write something along the lines of:

#include <functional>
#include <iostream>

template<class F>
F compose(F f, F g)
{
  return [=](int x) { return f(g(x)); };
}

int main()
{
  std::function<int (int)> f = [](int i) { return i * 2; };
  std::function<int (int)> g = [](int i) { return i + 10; };

  auto c = compose(f, g);
  std::cout << c(20) << '\n';  // prints 60
}

The code can be simply extended to cover the second half of the question:

template<class F>
F compose(F f, unsigned n)
{
  auto g = f;

  for (unsigned i = 0; i < n; ++i)
    g = compose(g, f);

  return g;
}

int main()
{
  std::function<int (int)> h = [](int i) { return i * i; };

  auto d = compose(h, 1);
  auto e = compose(h, 2);
  std::cout << d(3) << "\n"    // prints 81
            << e(3) << "\n";   // prints 6561
}

NOTE. Here using std::function. It isn't a lambda but wraps a lambda with a performance cost.




回答2:


A first attempt:

template<class First, class Second>
auto compose( Second&& second, First&& first ) }
  return [second = std::forward<Second>(second), first=std::forward<First>(first)]
  (auto&&...args)->decltype(auto) {
    return second( first( decltype(args)(args)... ) );
  };
}
template<class A, class B, class...Rest>
auto compose(A&& a, B&& b, Rest&&... rest) {
  return compose( compose(std::forward<A>(a), std::forward<B>(b)), std::forward<Rest>(rest)... );
}
template<class A>
std::decay_t<A> compose(A&& a) {
  return std::forward<A>(a);
}

in C++14. Now, this isn't perfect, as the pattern doesn't work all that well in C++.

To do this perfectly, we'd have to take a look at compositional programming. Here, functions interact with an abstract stack of arguments. Each function pops some number of arguments off the stack, then pops some number back on.

This would allow you do do this:

compose( print_coord, get_x, get_y )

where get_x and get_y consume nothing but return a coordinate, and print_coord takes two coordinates and prints them.

To emulate this in C++, we need some fancy machinery. Functions will return tuples (or tuple-likes?), and those values will be "pushed onto the argument stack" logically.

Functions will also consume things off this argument stack.

At each invocation, we unpack the current tuple of arguments, find the longest collection that the function can be called with, call it, get its return value, unpack it if it is a tuple, and then stick any such returned values back on the argument stack.

For this more advanced compose to compose with itself, it then needs SFINAE checks, and it needs to be able to take a invokable object and a tuple of arguments and find the right number of arguments to call the invokable object with, plus the left-over arguments.

This is a tricky bit of metaprogramming that I won't do here.


The second part, because I missed it the first time, looks like:

template<class F>
auto function_to_the_power( F&& f, unsigned count ) {
  return [f=std::forward<F>(f),count](auto&& x)
    -> std::decay_t< decltype( f(decltype(x)(x)) ) >
  {
    if (count == 0) return decltype(x)(x);
    auto r = f(decltype(x)(x));
    for (unsigned i = 1; i < count; ++i) {
      r = f( std::move(r) );
    }
    return r;
  };
}

This uses no type erasure.

Test code:

auto f = [](int x){ return x*3; };
auto fs = std::make_tuple(
    function_to_the_power( f, 0 ),
    function_to_the_power( f, 1 ),
    function_to_the_power( f, 2 ),
    function_to_the_power( f, 3 )
);

std::cout << std::get<0>(fs)(2) << "\n";    
std::cout << std::get<1>(fs)(2) << "\n";    
std::cout << std::get<2>(fs)(2) << "\n";    
std::cout << std::get<3>(fs)(2) << "\n";    

prints:

2
6
18
54


来源:https://stackoverflow.com/questions/33934687/is-it-possible-in-c11-to-combine-functions-into-a-new-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!