可组合编程或基于堆栈的编程模型可能是有意义的。
每个函数都接受一堆参数并返回相同的参数。这些堆栈可以是vector
或tuple
或生成器/通用范围。确定输入参数数量的一些方法可能是有用的。
在这种情况下,您的生成器会创建一个“本地”堆栈,然后将该堆栈扔给消费者,直到完成。
如果您希望这些操作交错,您可以让生产者函数返回一个生成器函数或一对生成迭代器,它们会延迟生成您的元素,或者您将其传递给输出堆栈,其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};
};
};
如果您更喜欢迭代器,将 aGenerator<T>
转换为生成迭代器只需编写一次,它适用于所有Generator<T>
. 并且编写Generator<>
基于代码的代码比编写基于迭代器的代码更容易。加Generator
s 和Pipe
s 很容易链接:
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
“更专业”。
这确实意味着 anystd::function< optional<T>()>
被视为生成器,这是不对的。可能 astruct generator_finished {}; variant< generator_finished, T >
是比 a 更好的方法optional
,因为generator_finished
当您不是生成器时在返回值的变体中使用似乎不礼貌。