Discussion:
[asio-users] Use of yield return expr in coroutines
Paulo Sequeira
2013-06-18 22:23:16 UTC
Permalink
Hi,

I've been looking at the coroutine idea from the http server4 and wanted
to try using it to make a simple generator. But I'm having trouble with
grasping the proper use of the "yield return expr" form and testing for
the completion of the coroutine.

For example, here's what I thought would be a simple generator that
would yield only one value and then complete:

struct int_gen : coroutine {
int operator()() {
reenter(this) {
yield return 1;
}
}
};

If I were using this generator, I would think that, after calling it
once, testing for is_complete() would return true and thus I would know
I shouldn't call into the generator again. But it seems is_complete() is
returning true one call-too-late. Like in what follows:

void main() {
int i = 3;
int_gen g;
i = g();
assert(i == 1);
i = 2;
assert(!g.is_complete());
i = g();
assert(g.is_complete());
//assert(i == ???);
}

Note that, after the first call to g(), g.is_complete() still returns
false; it is not until after the second call to g() that is_complete
returns true, but by that time, it already destroyed the contents of the
i variable.

What concerns me most is the fact that g() is running into undefined
behaviour because no return statement is actually being executed in the
second call, so there's something I'm not doing right at all. What would
be the appropriate way to code the generator with the help of the
coroutine class?

Best regards,

Paulo Sequeira
Igor Solovyov
2013-06-21 19:54:18 UTC
Permalink
Hi,

To make g.is_complete() returning true, the generator have to return
control by explicit return operator or implicitly by end of function.
While you return control by yield is_complete() returns true.

And your generator code is incorrect as a following function:
int foo( bool bar )
{
if ( bar )
{
return 1;
}
// return 0; <-- missed!
}
If you use -Wall in g++, you should see proper warning.

To make things right just add the final "return <something>" at the end of
your generator.

Regards,
Igor.
Post by Paulo Sequeira
Hi,
I've been looking at the coroutine idea from the http server4 and wanted
to try using it to make a simple generator. But I'm having trouble with
grasping the proper use of the "yield return expr" form and testing for
the completion of the coroutine.
For example, here's what I thought would be a simple generator that
struct int_gen : coroutine {
int operator()() {
reenter(this) {
yield return 1;
}
}
};
If I were using this generator, I would think that, after calling it
once, testing for is_complete() would return true and thus I would know
I shouldn't call into the generator again. But it seems is_complete() is
void main() {
int i = 3;
int_gen g;
i = g();
assert(i == 1);
i = 2;
assert(!g.is_complete());
i = g();
assert(g.is_complete());
//assert(i == ???);
}
Note that, after the first call to g(), g.is_complete() still returns
false; it is not until after the second call to g() that is_complete
returns true, but by that time, it already destroyed the contents of the
i variable.
What concerns me most is the fact that g() is running into undefined
behaviour because no return statement is actually being executed in the
second call, so there's something I'm not doing right at all. What would
be the appropriate way to code the generator with the help of the
coroutine class?
Best regards,
Paulo Sequeira
------------------------------------------------------------------------------
Build for Windows Store.
http://p.sf.net/sfu/windows-dev2dev
_______________________________________________
asio-users mailing list
https://lists.sourceforge.net/lists/listinfo/asio-users
_______________________________________________
Using Asio? List your project at
http://think-async.com/Asio/WhoIsUsingAsio
Paulo Sequeira
2013-06-26 17:25:44 UTC
Permalink
Post by Igor Solovyov
Hi,
To make g.is_complete() returning true, the generator have to return
control by explicit return operator or implicitly by end of function.
While you return control by yield is_complete() returns true.
int foo( bool bar )
{
if ( bar )
{
return 1;
}
// return 0; <-- missed!
}
If you use -Wall in g++, you should see proper warning.
To make things right just add the final "return <something>" at the
end of your generator.
I see, thank you.

What I was after is to try using Chris' stackless coroutines to see how
I could write a solution to the same fringe problem[1]. What I have
right now, thanks to Igor's explanation, is a variation of it that
answers whether the in-order scan of the trees are the same (reaching at
the same fringe solution is not far from this):

typedef struct node
{
int i;
struct node* left;
struct node* right;
} tree;

class tree_in_order : public stackless::coroutine {
tree const* m_t;
boost::scoped_ptr<tree_in_order> m_sub;

public:
tree_in_order(tree const* t)
: m_t(t)
, m_sub(NULL) {
if (!m_t)
CORO_REENTER(this) {} // mark as complete
}

int operator()() {
int r_;
CORO_REENTER(this) {
m_sub.reset(new tree_in_order(m_t->left));
while (!m_sub->is_complete()) {
CORO_YIELD return m_sub->operator()();
}

r_ = m_t->i;
m_sub.reset(new tree_in_order(m_t->right));
while (!m_sub->is_complete()) {
CORO_YIELD return r_;
r_ = m_sub->operator()();
}
}
return r_;
}
};

bool same_order(tree const* t1, tree const* t2) {
tree_in_order o1(t1);
tree_in_order o2(t2);

while (!o1.is_complete() && !o2.is_complete()) {
if (o1() != o2())
return false;
}

return o1.is_complete() == o2.is_complete();
}

I'd say same_order actually looks pretty good. It is the tree_in_order
code that looked somewhat odd at first, specially the fact that I have
to keep the r_ local around because I need to anticipate when
sub-coroutine on the right tree will be complete. However, after a while
it has started looking more reasonable to me. If someone has ideas to
improve how this was written, feel free to share in.

Pretty interesting this stackless coroutine stuff. Thanks a lot.

Paulo.

[1] http://c2.com/cgi/wiki?SameFringeProblem

Loading...