【问题标题】:Is it possible in C++11 to combine functions into a new function?在 C++11 中是否可以将函数组合成一个新函数?
【发布时间】:2016-02-29 07:35:12
【问题描述】:

这更像是一种理论问题。在 C++11 中是否可以将函数组合成一个新函数?例如:

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

所以这行得通:

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

但我想要一个存储组合的对象,比如

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

编辑: 另外我想要创建的是一个函数a,你可以给它一个函数b和一个int n,它返回给定函数b的第n个组合。例如(不可编译)

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

【问题讨论】:

  • 你不能做 auto c = [](int i) { return f(g(i)); }
  • 哦,是的,你是对的,但我忘记了一半的问题...... :( 无论如何谢谢。

标签: c++11 lambda functional-programming function-composition


【解决方案1】:

第一次尝试:

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

在 C++14 中。现在,这并不完美,因为该模式在 C++ 中并不能很好地工作。

要完美地做到这一点,我们必须看看组合编程。在这里,函数与抽象的参数堆栈交互。每个函数都会从堆栈中弹出一些参数,然后再弹出一些参数。

这将允许您这样做:

compose( print_coord, get_x, get_y )

其中get_xget_y 只消耗一个坐标,而print_coord 获取两个坐标并打印它们。

要在 C++ 中模拟这一点,我们需要一些花哨的机器。函数将返回 tuples(或 tuple-likes?),这些值将在逻辑上“推入参数堆栈”。

函数也会消耗这个参数堆栈中的东西。

在每次调用时,我们解包当前的参数元组,找到可以调用函数的最长集合,调用它,获取它的返回值,如果是元组则解包,然后粘贴任何这样的返回值返回参数堆栈。

为了让这个更高级的compose 与自身组合,它需要 SFINAE 检查,并且它需要能够接受一个可调用对象和一个参数元组,并找到正确数量的参数来调用可调用对象,加上剩余的参数。

这是一个棘手的元编程,我不会在这里做。


第二部分,因为第一次错过,长这样:

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

这不使用类型擦除。

测试代码:

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

打印:

2
6
18
54

【讨论】:

  • 皮尤,学到了很多新东西。不管怎样,你说服了我,我不想那样做。 ;)
  • @AquilaRapax 第一次作曲没有什么特别的错误。它只是不支持你想要的那么多。
【解决方案2】:

你可以这样写:

#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
}

代码可以简单地扩展以涵盖问题的后半部分:

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
}

注意。这里使用std::function。它不是 lambda,而是以性能成本包装 lambda。

【讨论】:

  • @AquilaRapax 请注意,此版本 (A) 不必要地复制函数对象,(B) 仅支持单个 int 参数,(C) 一次仅支持组合 2 个函数。其次,compose(F, unsigned) 函数如果传递一个 lambda(未包装在 std::function 中)作为其第一个参数将不起作用。它还执行 n 级类型擦除,这可能非常慢。但是,是的,它更简单。 :)
猜你喜欢
  • 2019-11-28
  • 2020-08-16
  • 1970-01-01
  • 2012-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多