C++ Logo

std-proposals

Advanced search

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

From: chuanqi.xcq <yedeng.yd_at_[hidden]>
Date: Mon, 08 Nov 2021 10:38:41 +0800
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;
}
```

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).

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

Received on 2021-11-07 20:38:47