Как "составить" функции, когда одна функция не дает 1 выходного параметра на 1 входной параметр?
Скажем, я хочу сочинять функции, такие как processResult
а также sendResult
, но я не могу просто связать их, потому что processResult
может понадобиться позвонить sendResult
0,1,2 или n раз за каждый звонок processResult
, Как правильно сделать это в C++11?
Я думал о 2 решениях:
1) дать первую функцию std::function
параметр (и назначить processResult
к нему), поэтому он может назвать его, когда это необходимо.
2) (не нравится это, кажется слишком сложным) потокобезопасная очередь, функции помещаются в 2 потока...
@ запросы например:
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 ответа
Композитное программирование, или стековое программирование, может иметь смысл.
Каждая функция принимает стек аргументов и возвращает одно и то же. Эти стеки могут быть vector
или же tuple
или генераторы / общие диапазоны. Некоторые средства для определения количества входных параметров могут быть полезны.
В этом случае ваш генератор создает "локальный" стек, затем вы бросаете этот стек потребителю до тех пор, пока не закончите.
Если вы хотите, чтобы эти операции чередовались, вы можете сделать так, чтобы функция продюсера возвращала функцию генератора или пару генерирующих итераторов, которые лениво производят ваши элементы, или вы передаете ему выходной стек, push
Метод переправляет его функции потребителя (т.е. передает функцию потребителя производителю).
Хорошая особенность решения генератора заключается в том, что оно не заполняет ваш стек выполнения с помощью crud, и вы лучше контролируете, что происходит дальше. Недостатком является то, что состояние производителя должно быть явно сохранено, а не жить в стеке, и что производитель должен быть достаточно сильно изменен. С лямбдами это не так уж и плохо, так как вы можете сохранить следующую итерацию цикла как замыкание, но это все еще сложно.
Вот тривиальный генератор:
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<>
на основе кода проще, чем написание генерирующего кода на основе итераторов. плюс Generator
с и Pipe
с легко цепочки:
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
в варианте вашего возвращаемого значения, когда вы не генератор, кажется невежливым.
Может быть, вы просто поместите свой sendResult в класс. Определите объект этого класса в классе процесса Result. Затем вы можете отправить sendResult столько раз, сколько хотите из результата процесса, используя этот объект.
Если вам просто нужно вызывать одну функцию из другой столько раз, сколько вы хотите, вы можете вызвать одну processResult
внутри sendResult
(и наоборот, при условии, что вы заранее объявите, что нужно).
int processResult() { /* blah blah */ }
void sendResult() {
while(needed) {
if(needed)
processResult();
}
}
Для меня это на самом деле не похоже на композицию функций, потому что если бы это было так, вы могли бы просто их связать. Из того, что я прочитал, вы требуете, чтобы processResult
должен иметь возможность вызывать некоторые внешние методы для дальнейшей обработки данных. В таком случае я бы подумал об этих двух решениях:
- Проходить
std::function
вprocessResult
, Это позволяет передавать в этот метод больше объектов: функции, объекта-функтора или даже лямбда-выражения. - Создайте интерфейс, обеспечивающий все необходимые операции, и передайте его процессу 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 может также получить другой класс, выполняющий фактическую отправку, так что вы можете "связать" больше зависимых объектов.
Многое зависит от архитектуры вашей программы, и я боюсь, что никто не сможет помочь вам в дальнейшем без какой-либо более подробной информации об этом.