当一个函数没有为1个输入参数提供1个输出参数时,如何“组合”函数?

时间:2013-06-05 10:56:30

标签: c++ c++11 function-composition

我想说我要撰写函数, 例如processResultsendResult,但我不能将它们链接起来因为processResult每次调用{sendResult可能需要调用processResult 0,1,2或n次1}}。 在C ++ 11中执行此操作的正确方法是什么?

我想到了两个解决方案:
1)给第一个函数一个std::function param(并为其指定processResult), 所以它可以在需要时调用它。

2)(不喜欢这个 - 似乎太复杂了)线程安全队列,把函数放在2个线程中......

@requests例如:

input 1,2,3
calls of functions:
processResult(1)
//nothing
processResult(2)
//calls:
sendResult(10)
sendResult(20)
sendResult(50)
processREsult(3)
//calls
sendREsult(55)

4 个答案:

答案 0 :(得分:4)

可组合编程或基于堆栈的编程模型可能有意义。

每个函数都使用一堆参数并返回相同的参数。这些堆栈可以是vectortuple或生成器/通用范围。确定输入参数数量的一些方法可能很有用。

在这种情况下,您的生成器会生成一个“本地”堆栈,然后将该堆栈抛给消费者,直到完成为止。

如果你想让这些操作交错,你可以让生成器函数返回一个生成器函数或生成迭代器的对,它们懒洋洋地生成你的元素,或者你传递一个输出堆栈,其push方法将它传递给它消费者功能(即将消费者功能传递给生产者)。

关于生成器解决方案的好处是它不会使用crud填充执行堆栈,并且您可以更好地控制接下来发生的事情。缺点是生产者状态需要显式存储,而不是生活在堆栈上,并且必须对生产者进行合理的修改。使用lambdas这并不是那么糟糕,因为你可以将循环的下一次迭代存储为闭包,但它仍然很棘手。

这是一个简单的生成器:

using boost::optional; // or std::optional in C++14
using boost::none_t;
template<typename T>
using Generator = std::function< optional<T>() >;

Generator<int> xrange( int min, int max, int step=1 ) {
  int next = min;
  return [=]()->optional<int> mutable
  {
    if (next > max) return {none_t};
    int retval = next;
    next += step;
    return {retval};
  };
};

如果您更喜欢迭代器,将Generator<T>转换为生成迭代器是您只需编写一次,并且适用于所有Generator<T>。编写基于Generator<>的代码比编写基于迭代器的代码更容易。加GeneratorPipe很容易链接:

template<typename T, typename U>
using Pipe = std::function< optional<T>(U) >;

template<typename A, typename B, typename C>
auto Compose( Pipe<A, B> second, Generator<C> first )
  -> decltype( second( std::move(*first()) ) )
{
  return [=]()->optional<A> mutable {
    optional<B> b = first();
    if (!b) return {none_t};
    return second(std::move(*b));
  };
}
template<typename A, typename B, typename C, typename D>
auto Compose( Pipe<A, B> second, Pipe<C, D> first )
  -> decltype( second( std::move(*first( std::declval<D>() ) ) ) )
{
  return [=](C c)->optional<A> mutable {
    optional<B> b = first(c);
    if (!b) return {none_t};
    return second(std::move(*b));
  };
}
// until C++14, when we get auto deduction of non-lambda return types:
#define RETURNS(x) -> declval(x) { return {x}; }
template<typename A, typename B, typename C>
auto operator|( Generator<A> first, Pipe<B,C> second )
  RETURNS( Compose(second, first) )

template<typename A, typename B, typename C, typename D>
auto operator|( Pipe<A, B> first, Pipe<C,D> second ) {
  RETURNS( Compose( second, first ) )

然后我们这样滥用:

struct empty {}; // easier to pass through pipes than void
template<typename T>
void sendEverything( Pipe<empty, T> sender, Generator<T> producer ) {
  Generator<empty> composed = producer | sender;
  while (composed()) {}
}

并且生产者愉快地生成数据,每个数据都发送给发送者,然后再次调用生产者。发件人甚至可以通过返回none_t来中止序列。

更高级的工作,我们能够拥有代表一对多和多对一关系的管道。

(代码尚未测试,因此可能包含编译器错误)

template<typename Out, typename In>
using OneToManyPipe = Pipe< Generator<Out>, In >;
template<typename Out, typename In>
using ManyToOnePipe = Pipe< Out, Generator<In> >;
template<typename Out, typename In>
using ManyToManyPipe = Pipe< Generator<Out>, Generator<In> >;

template<typename Out, typename A, typename B>
auto Compose( OneToManyPipe< Out, A > second, Generator<B> first )
  -> decltype( second( std::move(*first()) ) )
{
  auto sub_gen = [=]()->optional<Generator<Out>> mutable {
    optional<B> b = first();
    if (!b) return {none_t};
    return second(std::move(*b));
  };
  optional<Generator<Out>> sub = []()->optional<Out> { return {none_t}; };
  return [sub_gen,sub]()->optional<Out> mutable {
    for(;;) {
      if (!sub)
        return {none_t};
      optional<Out> retval = (*sub)();
      if (retval)
        return retval;
      sub = sub_gen();
    }
  }
}
template<typename Out, typename A, typename B, typename C>
auto Compose( OneToManyPipe< Out, A > second, OneToManyPipe<B, C> first )
  -> OneToManyPipe< decltype( *second( std::move(*first()) ) ), C >;
// etc

可能boost已经在某个地方做过了。 :)

这种方法的缺点是最终重载的运算符变得模糊不清。特别是,OneToMany管道和OneToOne管道之间的区别在于第二个是第一个管道的子类型。我认为optional<T>会使OneToMany“更加专业化。”

这意味着任何std::function< optional<T>()>都被视为生成器,不正确。可能struct generator_finished {}; variant< generator_finished, T >是一种比optional更好的方法,因为当您不是生成器时在返回值的变体中使用generator_finished似乎是不礼貌的。

答案 1 :(得分:0)

如果您只是需要多次调用另一个函数,可以processResult内调用一个sendResult(反之亦然,如果您转发 - 宣布需要什么。

int processResult() { /* blah blah */ }

void sendResult() { 
    while(needed) {
        if(needed)
           processResult();
    }
}

答案 2 :(得分:0)

这对我来说实际上看起来不像是函数组合,因为如果是这样的话,你就可以简单地链接它们。根据我的阅读,您需要processResult应该能够调用一些外部方法来进一步处理数据。在这种情况下,我会考虑这两个解决方案:

  • std::function传递给processResult。这允许您传递更多对象:函数,仿函数对象甚至lambda到该方法。
  • 创建一个界面,提供所有必需的操作并将其传递给processResult。
  • 将这两种方法更改为所谓的处理器类。如果它们是函数或方法,您将能够更灵活地使用它们,因此如果程序的复杂性增加,这个应该更适合您的需求。

    class VectorSender : IDataSender
    {
    private:
        std::vector<Data> & target;
    
    public:
        VectorSender(std::vector<Data> & newTarget)
            : target(newTarget)
        {
        }
    
        // Implementation of IDataSender
        void Send(Data data)
        {
            target.push_back(data);
        }
    };
    
    class Processor : IDataProcessor
    {
    private:
        sender : IDataSender;
    
    public:
        Processor(IDataSender newSender)
            : sender(newSender)
        {
        }
    
        // Implementation of IDataProcessor
        void Process()
        {
             // Do some processing
             if (sendData)
                  sender.Send(someData);
        }
    };
    

在前面的示例中,Sender还可以获得另一个执行实际发送的类,因此您可以“链接”更多依赖对象。

很大程度上取决于你的程序架构而且我担心,如果没有更详细的信息,没有人能够进一步帮助你。

答案 3 :(得分:0)

可能你就是把你的sendResult放在一堂课上。在进程Result的类中定义该类的对象。然后,您可以使用此对象从处理结果中多次发送sendResult。