C++ Logo

std-proposals

Advanced search

Re: Do we need a way to take *this by value, for coroutines?

From: Marcin Jaczewski <marcinjaczewski86_at_[hidden]>
Date: Mon, 8 Nov 2021 10:07:52 +0100
pon., 8 lis 2021 o 03:39 chuanqi.xcq via Std-Proposals
<std-proposals_at_[hidden]> napisał(a):
>
> Hi Phil,
>
> I am wondering if this could work around this problem:
> ```
> text_generator_t text() const // danger!
> {
> auto tmp_this = *this;
> for (auto c: *(tmp_this->p)) co_yield c;
> }
> ```
> or something like:
> ```
> text_generator_t text() const // danger!
> {
> auto tmp_p = p;
> for (auto c: *(tmp_p)) co_yield c;
> }
> ```

This will not work in every case as was mentioned previously, the
problem is that:
```
text_generator_t text() const // danger!
{
    // !!<<initial suspend here>!!
    auto tmp_p = p;
    for (auto c: *(tmp_p)) co_yield c;
}
```
This means all operations in the function body will be executed after
initial function call to `text()`. And this is too late as `this` will
be dead at this point.
>
> In this way, compiler should put `tmp*` variables into the coroutine frame so that the lifetime of `tmp` variables should extend to the same with the coroutine.
> But I admit that this didn't solve your problem foundamentally. The problem comes from that coroutine only copies argument and local variables into the frame.
> So it would be confusing in the cases of captured variables and the cases you mentioned for *this. But AFAIK, it isn't easy to solve. We couldn't require coroutine
> to copy every *this or others. It is expensive and semantic breaking. So we could only require the programmer to be carefull now.
> (Maybe the compiler could work hard to emit an warninig in some cases).
>
But as Giuseppe said, P0847 fix this problem as you can have value
based `this` with this we have:

```
text_generator_t text(this F self) //no `&`!
{
    for (auto c: *(self.p)) co_yield c;
}
```
This will use standard coroutine rules to copy the whole `self` object
to frame before any suspend.



> BTW, I think a mechanism to restrict a call to be called when *this by value didn't solve this kind of problem totally. For the example
> of Task:
> ```
> void f()
> {
> auto A = classA();
> auto task = A.get_coro_task(); // get_coro_task() is called on *this by value, so the compiler wouldn't complain
> submit_task_asynchronously(task); // task would resume after f() is end. In that time, A should be released.
> return; // So it may be problem if task would access A.
> }
> ```
> This is an example shows that the new mechanism couldn't solve the lifetime problems for coroutines. Since it is too complex and we had better
> to hope the programmer to be careful now.
>
> Thanks,
> Chuanqi
>
>
> ------------------------------------------------------------------
> From:Phil Endecott via Std-Proposals <std-proposals_at_[hidden]>
> Send Time:2021年11月8日(星期一) 02:05
> To:std-proposals <std-proposals_at_[hidden]>
> Cc:Phil Endecott <std_proposals_list_at_[hidden]>
> Subject:[std-proposals] Do we need a way to take *this by value, for coroutines?
>
> Dear Experts,
>
> I've recently been experimenting with coroutines for the
> first time. One issue that I've had some problems with is
> avoiding dangling this pointers when coroutine methods are
> used. As an example I had a generator that did a depth-first
> traversal of a tree yielding text (think HTML). I've
> simplified it to remove the recursion for the example below.
> (I'm using cppcoro's recursive_generator type, but this isn't
> specific to that.)
>
> #include <cppcoro/recursive_generator.hpp>
>
> #include <memory>
> #include <string>
>
> using text_generator_t = cppcoro::recursive_generator<const char>;
>
> class Element {
> std::shared_ptr<std::string> p;
>
> public:
> text_generator_t text() const // danger!
> {
> for (auto c: *p) co_yield c;
> }
>
> friend text_generator_t text2(Element e) // safe.
> {
> for (auto c: *(e.p)) co_yield c;
> }
> };
>
> class Document {
> public:
> Element root_element();
> };
>
> void f(Document doc)
> {
> auto text = doc.root_element().text(); // danger
> //auto text = text2(doc.root_element()); // safe
> std::string s{ text.begin(), text.end() };
> }
>
>
> Element is supposed to be a light-weight type storing a handle (a
> shared_ptr above) to the underlying data, so it's reasonable for
> Document to return an Element by value.
>
> The problem is that the Element returned by doc.root_element() has
> been destroyed by the time that we try to iterate the generator.
>
> If instead we had text() as a friend function we could take the
> Element by value and it would work.
>
> This is not unlike how this is captured by lambdas, where there have
> been changes so that *this can be captured by value.
>
> We have ref-qualifiers that allow us to indicate whether a method
> takes *this as an rvalue or lvalue reference, but this mechanism
> doesn't allow it to be taken by value.
>
> Some similar problems be fixed by taking a copy of *this at the start
> of the method, and there are similar approaches with shared_ptrs
> and enable_shared_from_this, but in a generator coroutine (initial
> suspend = suspend always) there is nowhere to do that.
>
> Comments anyone?
>
>
> Regards, Phil.
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>
>
> --
> Std-Proposals mailing list
> Std-Proposals_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals

Received on 2021-11-08 03:08:03