Last active
June 17, 2017 04:18
-
-
Save KayEss/999f8e3128dd1cd3255b688da8f04f9f to your computer and use it in GitHub Desktop.
Coroutine generator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* A generator. Initial code taken from N4649 p15 | |
* http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4649.pdf | |
*/ | |
#include <experimental/coroutine> | |
#include <iostream> | |
struct generator { | |
struct promise_type; | |
using handle = std::experimental::coroutine_handle<promise_type>; | |
struct promise_type { | |
int current_value; | |
static auto get_return_object_on_allocation_failure() { | |
return generator{nullptr}; | |
} | |
auto get_return_object() { | |
return generator{handle::from_promise(*this)}; | |
} | |
auto initial_suspend() { | |
return std::experimental::suspend_always{}; | |
} | |
auto final_suspend() { | |
return std::experimental::suspend_always{}; | |
} | |
auto yield_value(int value) { | |
std::cout << "Yielded " << value << std::endl; | |
current_value = value; | |
return std::experimental::suspend_always{}; | |
} | |
auto return_void() { | |
return std::experimental::suspend_never{}; | |
} | |
void unhandled_exception() { | |
std::exit(1); | |
} | |
}; | |
bool move_next() { | |
if ( coro ) { | |
std::cout << "Moving to next" << std::endl;; | |
coro.resume(); | |
std::cout << "Are we done? "; | |
auto still_going = not coro.done(); | |
std::cout << (still_going ? "There is another" : "We're done") << std::endl; | |
return still_going; | |
} else { | |
return false; | |
} | |
} | |
int current_value() { | |
return coro.promise().current_value; | |
} | |
generator(const generator &) = delete; | |
generator(generator &&g) | |
: coro(g.coro) { | |
g.coro = nullptr; | |
}; | |
~generator() { | |
if ( coro ) coro.destroy(); | |
} | |
private: | |
generator(handle h) | |
: coro(h) { | |
} | |
handle coro; | |
}; | |
generator f() { | |
std::cout << "Going to yield 1" << std::endl; | |
co_yield 1; | |
std::cout << "Going to yield 2" << std::endl; | |
co_yield 2; | |
std::cout << "Going to yield 3" << std::endl; | |
co_yield 3; | |
std::cout << "f() is done" << std::endl; | |
} | |
int main() { | |
std::cout << "coro3" << std::endl; | |
auto g = f(); | |
while ( g.move_next() ) | |
std::cout << ">> " << g.current_value() << std::endl; | |
return 0; | |
} | |
There is a subtle bug in the code, to do with the way the generator
is destructed. Because the destructor will destroy the coroutine we have to be careful about copying the generator
instance itself. Making it movable and not copyable fixes this:
generator(const generator &) = delete;
generator(generator &&g)
: coro(g.coro) {
g.coro = nullptr;
};
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The output from this is (for release builds):
So it doesn't actually work.
Debug works, but gets a double free/memory corruption error on completion.