cppcoro icon indicating copy to clipboard operation
cppcoro copied to clipboard

A generator that returns some terminal value

Open Anton3 opened this issue 4 years ago • 2 comments

Sometimes I want to co_return some additional final value from a generator. It could be:

  • Some additional statistics on the data as a whole
  • A reason for why the stream of values has ended

Such generator could also model a simple coroutine-style interation: When the coroutine needs the caller to provide further guidance on what it needs to do, it co_yields a request for that guidance. When the work is finished, the coroutine will return a value.

More specific examples:

  • "ping" utility (co_yields pong responses, co_returns statistics)
  • Video stream (co_yields frames, co_returns reason for stopping: video ended, network troubles)
  • Camera controller (co_yields new camera configuration, co_returns a final, good enough shot)

So I need some generator_with_result<T, R> that is a range and can be co_awaited. Also asynchronous and recursive versions may be useful.

Anton3 avatar Dec 30 '19 20:12 Anton3

It should definitely be possible to build such a coroutine type, at least from the consumer’s point of view.

It would require a bit more thought on how to design the api for the consumer, however.

There are a few options i can think of.

  1. Expose it as a range of variant<T,R> (assuming those types are different) and then always have the R value be the end of the sequence.

  2. Use a different API design than begin/end/iterators. Eg have a next() method that returned a result<T,R> that indicates whether the result is an element of the sequence or the sentinel value.

auto gen = make_generator();
while (true) {
  if (auto res = co_await gen.next(); res.has_value()) {
    use(res.value());
  } else {
    use(res.sentinel());
    break;
  }
}

lewissbaker avatar Dec 31 '19 01:12 lewissbaker

You would usually want to handle values inside the loop and the sentinel outside the loop:

auto gen = make_generator();
while (co_await gen.next()) {
  use(gen.value());
}
use(gen.sentinel());

So I believe a better interface would be:

  • gen.next() executes the coroutine, stores inside gen a void* pointing to a T or to an R, returns true if it's a T
  • gen.value(), gen.sentinel() access that void*

Also, the generator_with_result<T, R> name I suggested is not very good, it could be sentinel_generator<T, R> or generator_task<T, R>.

Anton3 avatar Dec 31 '19 10:12 Anton3