Boost logo

Boost :

From: Klemens Morgenstern (klemensdavidmorgenstern_at_[hidden])
Date: 2023-09-24 15:36:04


On Sun, Sep 24, 2023 at 11:24 PM Andrzej Krzemienski <akrzemi1_at_[hidden]> wrote:
>
>
>
> niedz., 24 wrz 2023 o 16:17 Klemens Morgenstern <klemensdavidmorgenstern_at_[hidden]> 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.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk