8

This question stems from this comment: Lambda lifetime explanation for C++20 coroutines

regarding this example:

auto foo() -> folly::coro::Task<int> {
    auto task = []() -> folly::coro::Task<int> {
        co_return 1;
    }();
    return task;
}

So the question is whether executing the coroutine returned by foo would result in UB.

"Calling" a member function (after the object's lifetime ended) is UB: http://eel.is/c++draft/basic.life#6.2

...any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. [...] The program has undefined behavior if:

[...]

-- the pointer is used to access a non-static data member or call a non-static member function of the object, or

However, in this example:

  • the () operator of the lambda is called while the lifetime of the lambda is still valid
  • It is then suspended,
  • then the lambda is destroyed,
  • and then the member function (operator ()) is resumed at some point afterwards.

Is this resumption considered undefined behavior?

Mike Lui
  • 1,301
  • 10
  • 23
  • 2
    Maybe the following answer is relevant https://stackoverflow.com/a/60495359/12345656 It seems pretty different, but it also is about a member function during which execution the `this` pointer is invalidated. Consider also the discussion in the comments. – n314159 Mar 11 '20 at 14:08

1 Answers1

2

[dcl.fct.def.coroutine]p3:

The promise type of a coroutine is std::coroutine_traits<R, P1, ..., Pn>::promise_type, where R is the return type of the function, and P1 ... Pn are the sequence of types of the function parameters, preceded by the type of the implicit object parameter (12.4.1) if the coroutine is a non-static member function.

The implicit object parameter is in your example a const reference, and hence that reference will be dangling when execution is resumed after the closure object has been destroyed.

However, on the note of objects being destroyed during execution of a member function, this is indeed fine per se, and no other than the standard itself implies this in [basic]:

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any pointer that represents the address of the storage location where the object will be or was located may be used but only in limited ways. [...]

void B::mutate() {
  new (this) D2;    // reuses storage --- ends the lifetime of *this
  f();              // undefined behavior
  ... = this;       // OK, this points to valid memory
}

(NB: the above UB is because the implicit this is not laundered and still refers to the implicit object parameter.)

So your example appears to be well-defined, conditional on the idea that resumption of execution does not fall under the same rules as an original invocation. Note that the reference to the closure object might be dangling, but it's not accessed in any way between suspension and resumption.

Columbo
  • 60,038
  • 8
  • 155
  • 203
  • Do you mean “resumption and completion” at the end? – Davis Herring Mar 11 '20 at 18:38
  • @DavisHerring No, I meant specifically within that "outside" time frame, where it's not clear whether the reference could be assigned to a new reference etc. which would require a real object. The fact that the reference is not accessed in a hidden manner is important for this not to be UB – Columbo Mar 11 '20 at 18:43
  • But it’s not enough to leave the dangling reference alone until resumption; you have to leave it alone (*e.g.*, in the lambda body) forever—for the rest of its lifetime, which is until completion. So maybe it should be “suspension and completion”. – Davis Herring Mar 11 '20 at 18:46
  • @DavisHerring I specifically mentioned that interval, because in our example we know the other one to be safe. – Columbo Mar 12 '20 at 09:05
  • Sure; I just find the wording confusing. Maybe no one else does. – Davis Herring Mar 12 '20 at 13:23