According to the documentation:
- push_type does not enter coroutine-function on construction;
- the first call of push_type::operator() synthesizes a complementary pull_type and passes it as reference to coroutine-function
But what happens if:
push_type coro([](pull_type&){ return; });
- the user finds that
bool(coro) == true (or: begin(coro) != end(coro)) so the user would like to perform coro(value_of_T) (or: *begin(coro) = value_of_T)
It seems the value will be lost.
The problem is: you don't know whether an output range is empty unless you push a value to it first.
(Edit: sorry for the mistake, it's the last value that will always be lost if the output range is not infinite.)
#include<boost/coroutine2/all.hpp>
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<utility>
int main()
{
typedef boost::coroutines2::coroutine<std::string> coro_t;
struct FinalEOL {
~FinalEOL() {
std::cout << std::endl;
}
};
const int num = 5, width = 15;
coro_t::push_type writer(
[&](coro_t::pull_type& in) {
//// finish the last line when we leave by whatever means
//FinalEOL eol;
// pull values from upstream, lay them out 'num' to a line
//for (;;) {
// for (int i = 0; i < num; ++i) {
// // when we exhaust the input, stop
// if (!in) return;
// std::cout << std::setw(width) << in.get();
// // now that we've handled this item, advance to next
// in();
// }
// // after 'num' items, line break
// std::cout << std::endl;
//}
});
std::vector<std::string> words{
"peas", "porridge", "hot", "peas",
"porridge", "cold", "peas", "porridge",
"in", "the", "pot", "nine",
"days", "old" };
//std::copy(begin(words), end(words), begin(writer));
// safe version of std::copy that takes care of end(dest)
auto [words_it, writer_it] = std::pair{ begin(words),begin(writer) };
for (; words_it != end(words) && writer_it != end(writer); ++words_it, ++writer_it)
{
*writer_it = *words_it;
}
// print remaining words; the first word is lost
for (; words_it != end(words); ++words_it)
{
std::cout << *words_it << "\t";
}
}
An applicable approach would be to resume the coroutine in begin(coro) like https://github.com/lewissbaker/cppcoro#generatort .
Here is the current API and the imagined API:
current API:
|
pull_type |
framework-generated push_type for pull_type |
push_type |
framework-generated pull_type for push_type |
| constructor |
enter coroutine-function |
|
|
|
operator() iterator::operator++() |
resume coroutine-function |
|
|
suspend coroutine-function |
| get() |
transfer data |
|
|
transfer data |
operator() iterator::operator=() |
|
transfer data, suspend coroutine-function |
(first time: transfer data, enter coroutine-function) transfer data, resume coroutine-function |
|
imagined API:
|
pull_type |
framework-generated push_type for pull_type |
push_type |
framework-generated pull_type for push_type |
| constructor |
|
|
|
|
start() begin(coro) |
①enter coroutine-function |
② |
⒈enter coroutine-function |
⒉suspend coroutine-function |
advance() iterator::operator++() |
⑥⑩resume coroutine-function |
④⑧suspend coroutine-function |
⒋⒏resume coroutine-function |
⒍⒑suspend coroutine-function |
| get() |
⑤⑨transfer data |
|
|
⒌⒐transfer data |
set() iterator::operator=() |
|
③⑦transfer data |
⒊⒎transfer data |
|
Note that in the column of push_type: if after ⒊transfer data instead of ⒋resume coroutine-function you ⒋abandon the coroutine, then the value_of_T will still be lost. But this time it is the user of API to blame, not the framework.
These are what you can do:
| utility |
current API |
imagined API |
merge pipe and source into source
void(pull_type<T1>,push_type<T2>) + pull_type<T1> -> pull_type<T2> |
trivial for loop |
trivial for loop |
merge pipe and source into source
T2(T1) + pull_type<T1> -> pull_type<T2> |
trivial for loop |
trivial for loop |
merge pipe and sink into sink
void(pull_type<T1>,push_type<T2>) + push_type<T2> -> push_type<T1> |
trivial for loop |
trivial for loop |
merge pipe and sink into sink
T2(T1) + push_type<T2> -> push_type<T1> |
trivial for loop |
trivial for loop |
merge source and sink and stops when either one exhausts
std::pair<source_it_t, sink_it_t> safe_copy(source_begin, source_end, sink_begin, sink_end) |
the first value of source will be lost if sink is an empty range |
trivial for loop the user will not lose the first value of source because if sink is an empty range then we have begin(sink) == end(sink) after construction and start()/begin(sink) |
According to the documentation:
But what happens if:
push_type coro([](pull_type&){ return; });bool(coro) == true(or:begin(coro) != end(coro)) so the user would like to performcoro(value_of_T)(or:*begin(coro) = value_of_T)It seems the value will be lost.
The problem is: you don't know whether an output range is empty unless you push a value to it first.
(Edit: sorry for the mistake, it's the last value that will always be lost if the output range is not infinite.)
An applicable approach would be to resume the coroutine in
begin(coro)like https://github.com/lewissbaker/cppcoro#generatort .Here is the current API and the imagined API:
current API:
iterator::operator++()
iterator::operator=()
transfer data, resume coroutine-function
imagined API:
begin(coro)
iterator::operator++()
iterator::operator=()
Note that in the column of
push_type: if after⒊transfer datainstead of⒋resume coroutine-functionyou⒋abandon the coroutine, then thevalue_of_Twill still be lost. But this time it is the user of API to blame, not the framework.These are what you can do:
void(pull_type<T1>,push_type<T2>) + pull_type<T1> -> pull_type<T2>T2(T1) + pull_type<T1> -> pull_type<T2>void(pull_type<T1>,push_type<T2>) + push_type<T2> -> push_type<T1>T2(T1) + push_type<T2> -> push_type<T1>std::pair<source_it_t, sink_it_t> safe_copy(source_begin, source_end, sink_begin, sink_end)the user will not lose the first value of source because if sink is an empty range then we have
begin(sink) == end(sink)after construction and start()/begin(sink)