[prev in list] [next in list] [prev in thread] [next in thread] 

List:       boost
Subject:    Re: [boost] [boost.async] Dummy co_return from a generator
From:       Klemens Morgenstern via Boost <boost () lists ! boost ! org>
Date:       2023-09-24 15:36:04
Message-ID: CAGT5OKMRxaRND-Ceq-z0Yn13muPcORUryW1p7KFxRN-Ocr3=vA () mail ! gmail ! com
[Download RAW message or body]

On Sun, Sep 24, 2023 at 11:24 PM Andrzej Krzemienski <akrzemi1@gmail.com> wrote:
> 
> 
> 
> niedz., 24 wrz 2023 o 16:17 Klemens Morgenstern <klemensdavidmorgenstern@gmail.com> \
> napisał(a):
> > 
> > > > 
> > > > Why? How would you want to communicate to the awaiter that the coro is done?
> > > 
> > > 
> > > First, there are a bunch of use cases where the consumer of the generator \
> > > doesn't need to know, like the one with the listener: keep generating until you \
> > > are canceled.
> > 
> > Ok, so it's technically UB, but you can skip the co_return. That will
> > however generate a warning on msvc, so it's not officially recommended
> > or mentioned in the docs.
> > But I tested it on all compilers and it seemed to work.
> 
> 
> It is only UB if the control reaches the end of the body.
> But if I know the loop is infinite and I will be always canceling the coroutine \
> (calling .destroy()), reaching the end of function body does not happen, and \
> therefore no UB. 
> > 
> > 
> > This is an issue with the C++ API. A coroutine promise can either have
> > a return_value OR a return_void. I cannot have both at the same time.
> > If that was possible, I'd do it.
> 
> 
> OK, I now understand why std::generator does not trigger an analogous warning in \
> MSVC. std::generator<T>::promise_type defines the pair yield_value() and \
> return_void(). So you can yield a value from a std::generator but you cannot return \
> one. 
> Question: Is it important to the design of async::generator to allow `co_return \
> value`? All the examples of coroutines I have ever seen yield values in a loop. 

I think so, especially for users that do not want to use exceptions
for errors. So being able to co_return an error instead of
co_yield-ing a value (e.g. using system::result) seems quite useful

> > 
> > > Second, std::generator somehow does it. (I do not know how.)
> > 
> > It does it by using iterators. I.e. you advance the iterator, which
> > will resume the coroutine. Then you check against end() if it
> > co_returned (void) and then you get the cached value.
> > 
> > The async::generator does it in one call.
> 
> 
> Yeah, so the interface of std::generator is similar to returning optional<T> form \
> async::generator. In the sense that it returns two pieces of information: (1) \
> whether we are at the end, (2) and if not, what value we have. 
> > 
> > 
> > > Back to your question, when I see a code structure like this:
> > > 
> > > async::generator<T> fun()
> > > {
> > > while(cond)
> > > {
> > > co_yield something;
> > > }
> > > }
> > > 
> > > I know that there is nothing to do after the last co_yield in the loop. So \
> > > maybe the compiler/library should also. Modulo that this may not be doable in \
> > > the library, or the hacks are too expensive.
> > 
> > It's doable, but the price is more API complication. I think a dummy
> > return while annoying is the best solution.
> > Because otherwise the user needs to use a different generator type if
> > he wants to use a significant value return as indication he's done
> > (e.g. a generator<system::result<size_t>>.).
> 
> 
> Given the present interface, I have two ways of checking the "I am done" state:
> 1. One is to call generator::operator bool()
> 2. The other is to inspect the state of the yielded value.
> 
> I see no use for the first one. There is an `operator bool` that doesn't do the \
> job. Or did I misunderstand again?

It'll tell you if it co_returned. So it's useful.

> 
> Would it be correct to say that I cannot use `async::generator<T>` effectively when \
> my type `T` doesn't have a special dummy state?

No. Throwing an exception or skipping the co_return (ignoring the MSVC
warning) would solve that issue.

> 
> > 
> > The asio::experimental::coro defaults to using an optional btw., which
> > I find much more cumbersome as a default, especially with the example
> > above.
> 
> 
> I am aware of two use cases. One where the resumer decides when the generation \
> ends, the other when it is the generator that decides. For the former case, you are \
> right. For the latter case, using optional is no worse than testing the dummy value \
> on one side, and putting an additional code to generate the dummy value on the \
> other. 

Sure, but from the API perspectice, you can just use a
generator<std::optional<T>> that co_yields T and co_returns
std::nullopt. I don't see the issue with an additional dummy co_return
at the end.

_______________________________________________
Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic