2

I've been working with c++20 coroutines and I stumbled upon this issue with the lifetime of the lambda captures not extending for the entire life of the coroutine.

I was wondering what's safe to capture, since I've been having to copy all my captures into new objects like this:

[a1=object]() -> task<void> {
    // need to copy into a new object to safely reference for the lifetime of the coroutine
    auto object = a1;
    co_await something;
    // ...

When I captured this explicitly in my program:

[this]() -> {
    co_await something;
    this->....

I was able to reference this after a suspend with no issue.

However, when reading the standard, I found this:

An entity is captured by reference if it is implicitly or explicitly captured but not captured by copy. It is unspecified whether additional unnamed non-static data members are declared in the closure type for entities captured by reference.

Given that whether it creates the pointer as a property is "unspecified", does this mean I just got lucky? Or is there something different about this captures?

Enlico
  • 23,259
  • 6
  • 48
  • 102
lufinkey
  • 342
  • 4
  • 15
  • As long as the lambda isn't used after the object is destroyed, of the lambda doesn't use the object after the object is destroyed, there will be no problem. Anything else is either lucky (program crashes forthwith), or unlucky (program appears to work). – Eljay Aug 03 '21 at 01:26
  • 1
    @Eljay Well this is more the opposite of what you're talking about. With C++20 coroutines, the coroutine lives on longer than the captured lambda variables. So this is a little different. Check my first link – lufinkey Aug 03 '21 at 02:12
  • 1
    The coroutine has undefined behaviour if it dereferences the `this` pointer (e.g. calls an object's non-static member function) after the end of life of the pointed-to object. – Peter Aug 03 '21 at 03:30
  • 1
    But what about after the end of the life of the lambda? Since the coroutine lives on for longer than the lambda. Is there any possibility of it trying to read a pointer address of `this` stored on the lambda capture and causing a segfault? – lufinkey Aug 03 '21 at 14:39

1 Answers1

0

Programmers should ignore that sentence in the standard: it merely allows implementations to allocate less memory than might naïvely be expected for lambda objects with reference captures (especially when the call operator is inlined).

Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Do references just always get copied to the stack then, once the lambda executes? – lufinkey Aug 04 '21 at 15:11
  • @lufinkey: If they must be stored (*e.g.*, for use via `std::function`), they’ll be in the closure object just like any other capture (although some for local variables and parameters might get merged into a pointer to the whole stack frame). Otherwise, they just don’t really exist: the compiler just already knows where the captured entities are. – Davis Herring Aug 04 '21 at 19:32
  • 1
    I'm not sure I really understand. I'm just trying to not be accessing dead memory during my coroutine. If I write a *coroutine* that captures an object like this, I need to perform a copy to the stack upon entering the coroutine like this: `[a1=someObject]() -> task { auto someObject = a1; co_await something; /* attempt to access someObject */ }` Otherwise accessing `someObject` points to dead memory. I have found that this *doesn't* need to happen when capturing `this`. I don't understand why. The standard seems very unclear – lufinkey Aug 04 '21 at 20:13
  • I don't think this is a situation that needs `std::function` unless you're doing something like the hack fix I did [here](https://stackoverflow.com/a/68630143/1846536). But I'm more just wondering about the semantics than a fix. – lufinkey Aug 04 '21 at 20:17
  • Are you just saying references get copied to the stack or no? – lufinkey Aug 04 '21 at 20:18
  • 1
    @lufinkey: `[this]` just captures `*this` (*i.e.*, the object for which the member function was called) by reference. If that **object** is alive, regardless of whether the particular **call** has returned, it and its members are usable. The reference itself is always *available* (even though it might be dangling) because it’s part of the lambda—thinking about the stack, or how the implementation actually finds the reference, is just a distraction. (In the common case where everything is inlined, the call can’t have returned, but `delete this;` could still invalidate the `[this]` capture.) – Davis Herring Aug 04 '21 at 21:21
  • 1
    @DavisHerring - I don't think the it is true that "reference itself is always available e (even though it might be dangling) because it’s part of the lambda": the lambda itself can be destroyed, making the captured values themselves invalid to access, i.e., if you capture `a` by reference like `[&a]()`, it might be that `a` itself is still alive, but using `a` inside the lambda is UB since the labda itself has been destroyed, hence the reference to `a` (likely implemented as a pointer inside the closure) is destroyed. – BeeOnRope Mar 05 '22 at 01:17
  • 1
    That problem occurs relatively frequently for lambdas because of how they are used and is explained in [the link](https://stackoverflow.com/questions/60592174/lambda-lifetime-explanation-for-c20-coroutines) @lufinkey provided in the first post. So I think the question might be phrased as: local variables declared inside and formal parameters to a lambda-coroutine live at least as long as the _coroutine capture_ but _lambda captures_ generally live only as long as the lambda object. Therefore it is safe to use the former but not the latter in the cases the coroutine capture \ – BeeOnRope Mar 05 '22 at 01:22
  • outlives the lambda object. I would expect captures of `this` to act as any other by-reference lambda capture, but as @lufinkey (i.e., the second case, unsafe in the scenario being discussed) but actually in clang they seem to behave as if they were locals inside the lambda-coroutine, but it is isn't clear if this is guaranteed or a quirk of clang's implementation. – BeeOnRope Mar 05 '22 at 01:24
  • @BeeOnRope: I don’t think we’re in disagreement: the answer says that references are not (reliably) special, so the same precautions need to be taken if the closure object might have been destroyed. In the comments I was addressing the possibility that the validity of a reference capture might somehow be bound to the lifetime of the function call in which the closure was created rather than to the lifetime of the referent. I might have been missing the point there, though, since the lifetime of the closure itself is often no longer than that. – Davis Herring Mar 05 '22 at 03:05
  • 1
    @DavisHerring - I had a longer reply but lost it. I think one concrete complaint I have about the answer itself (not the comments), is that it does not answer the headline question "is it safe to...". I think the answer you are implying by responding only to that confusing bit of text in the standard, is that no it is not safe to capture `this` any more than any other capture by refefence, as shown by the OP: the lambda object will often be destroyed after the first suspension point, leaving `this` dangling (not in the sense of the object that `this` points to being destroyed, but the `this` – BeeOnRope Mar 22 '22 at 23:49
  • 1
    reference itself has been destroyed). clang's behavior which stores `this` (but not other captures, whether by reference or value) on the coroutine frame instead, apparently making it safe, is unexplained and not supported by the standard. – BeeOnRope Mar 22 '22 at 23:50