C++ Logo

std-proposals

Advanced search

Re: async coroutines vs. lambdas

From: Avi Kivity <avi_at_[hidden]>
Date: Thu, 14 May 2020 19:01:01 +0300
On 14/05/2020 18.33, Marcin Jaczewski wrote:
> czw., 14 maj 2020 o 17:02 Avi Kivity <avi_at_[hidden]> napisał(a):
>>
>> On 14/05/2020 17.54, Marcin Jaczewski wrote:
>>> czw., 14 maj 2020 o 16:11 Avi Kivity <avi_at_[hidden]> napisał(a):
>>>> On 14/05/2020 16.56, Marcin Jaczewski wrote:
>>>>> czw., 14 maj 2020 o 15:43 Avi Kivity <avi_at_[hidden]> napisał(a):
>>>>>> On 14/05/2020 16.30, Marcin Jaczewski via Std-Proposals wrote:
>>>>>>> And code like:
>>>>>>>
>>>>>>> ```
>>>>>>> lazy<int> get_custom_lambda() {
>>>>>>> struct X
>>>>>>> {
>>>>>>> int i = 3;
>>>>>>> lazy<int> operator()(){ co_return i; }
>>>>>>> } x();
>>>>>>> return x();
>>>>>>> }
>>>>>>>
>>>>>>> ```
>>>>>>>
>>>>>>> This still crash, and this is same code as `get_real_lambda`.
>>>>>>> How do you like fix it?
>>>>>> If the coroutine is a member function, then
>>>>>>
>>>>>> If the member function was called on an rvalue object, the object is
>>>>>> captured in the coroutine frame by value (and is not a reference in
>>>>>> coroutine_trait's type parameters)
>>>>>>
>>>>>> If the member function was called on an lvalue object, the object is
>>>>>> captured by reference (as the current standard requires).
>>>>>>
>>>>>>
>>>>>>> and what if lambda is not movable?
>>>>>> If the lambda cannot be moved or copied to the coroutine frame, then the
>>>>>> program is ill formed (same as passing a non-copyable, non-movable
>>>>>> argument).
>>>>>>
>>>>>>
>>>>>>
>>>>> But then corutine can see only this:
>>>>>
>>>>> ```
>>>>> lazy<int> X::operator()() const
>>>>> {
>>>>> co_return this->i;
>>>>> }
>>>>> ```
>>>>> There is no info if you have rvalue or lvalue; If we had
>>>>> `X::operator()() &&` then it could work, but this will need lot more
>>>> We do have X::operator()():
>>>>
>>>>
>>>> https://godbolt.org/z/_YSsB4
>>>>
>>>>
>>>>> changes than you propose.
>>>> For a lambda, the compiler can see that it is called as an rvalue and
>>>> tack on && by itself.
>>>>
>>>>
>>> My point was that AFIK current lambda do not have `&&` and adding it
>>> could be visible for user in other contexts,
>>> If we can safely add new overload and change new case for corutines
>>> that copy move `*this` if function is defined with `&&` then this
>>> problem is fixed.
>>> Other wise we should stick to current behavior and do not returns
>>> corutines from local objects.
>>>
>>> btw in case you show:
>>> ```
>>> int bar;
>>>
>>> get_int().then([foo] (int val) -> future<> {
>>>
>>> int another = co_await get_int();
>>>
>>> bar = foo + val + another;
>>>
>>> });
>>> ```
>>> is different case, because do you not call `operator()()` there.
>>>
>>> if `then` look like this:
>>> ```
>>> task<> then(T call)
>>> {
>>> co_await call();
>>> }
>>> ```
>>> Then you do not have problem with not coping lambda. because `call`
>>> will be embedded in context of `then`.
>>
>> Yes, but I don't want to create a coroutine frame in then(). Most of the
>> arguments to my then() are plain lambdas, not coroutines. They are
>> perfectly happy with the lambda being destroyed once its operator()()
>> returns. It would be a huge source of new allocations.
>>
>>
>
> Then why do not use `auto val = co_await get_int();`? your code need
> live till `bar = foo + val + another;` and you do allocation in this
> code already.


Of course a.then(b) can be folded together into a single coroutine
(faster and simpler). But I have many other primitives.
smp::submit_to(func) runs a function on another core.
parallel_for_each(container, func) will run func in parallel for each
element (just on this core). Some primitives internally use .then() to
chain calls, so I can't do the merging.


Working with lambdas was a minefield of use-after-frees, I was hoping
coroutines would free me of that, but it turns out to be just another
minefield.

Received on 2020-05-14 11:04:08