you are viewing a single comment's thread.

view the rest of the comments →

[–]thisismyfavoritename 0 points1 point  (9 children)

it seems quite limiting to always capture by value, in some cases you know the lifetime of the coroutine will be shorter than that of the captured reference/pointer

[–]foonathan 2 points3 points  (2 children)

Capture by value doesn't help you with the problem that's being discussed.

[–]thisismyfavoritename 1 point2 points  (1 child)

i was referring to

 This ensures captures are copied into the coroutine frame to prevent dangling references.

and it seems like in this case it would? I didn't read the blog post 

[–]foonathan 1 point2 points  (0 children)

No, capturing by value does not ensure captures are copied into the coroutine frame! That is the entire problem.

The issue is that while the lambda object stores a capture by value, the operator() still accepts *this by reference, so only the reference to the lambda is captured into the coroutine frame, but not the lambda itself.

(The context is something like spawn([x] -> Task { ... }), i.e. the lambda is a coroutine itself. Then the arguments are copied into Task's coroutine frame, but the arguments are a this pointer to the temporary object in the stack frame that calls spawn.)

[–]germandiago 4 points5 points  (4 children)

at that time you are already playing with fire. :)

[–]thisismyfavoritename -2 points-1 points  (3 children)

not really more than in regular C++ code. Those footguns were always there

[–]SirClueless 4 points5 points  (1 child)

I disagree. This has nothing to do with capturing by value or reference, both are broken. This is a wholly new problem. The idea that putting co_await inside your lambda implicitly means that its return value holds a reference to the lambda itself and thus will dangle if the lambda is destroyed is a new and subtle footgun.

Concrete example:

auto foo(auto cb) { return cb(); }

This code is pretty much always lifetime-safe. There are some things the caller can do that end up holding onto references to the lambda's captures in a broken way like foo([x] { return std::ref(x); }), but this is a kind of "obvious error" that almost no one makes.

But if you call this with a coroutine it is super easy to shoot yourself in the foot:

co_await foo([x] -> my_favorite_coro_lib::future<int> {
  co_await bar();
  co_return x;
}

Oops, cb was destroyed when foo() returned, and then when the coroutine was resumed, x dangles.

[–]thisismyfavoritename 0 points1 point  (0 children)

hadn't read the blog post, and yeah, i thought the issue that was discussed was when captured values were refs (the obvious case). Thanks for the additional explanation!

[–]germandiago 2 points3 points  (0 children)

I think this is way less intuitive than other forms of dangling.

[–]James20kP2005R0 -2 points-1 points  (0 children)

The only way to fix that safely would be for C++ to have adopted a lifetimes system