Coroutine TS
A new way of thinking
Meeting C++ 2018
Rebuild language project
C++17 Coroutine-TS
- Who read about it?
C++17 Coroutine-TS
- Who tried it?
What is a Coroutine?
- generalized function
- with continuations
yield
- suspend coroutine
- intermediate
return
What is it for?
- generators
- state machines
- async tasks
- … your imagination …
Generators
Generators ?
- produce next result on demand
Simple Coroutine
auto generate() -> Generator<long> {co_yield 23;co_yield 42;}
void test_generate() {auto f = generate();cout << static_cast<bool>(f) << '\n'; // true++f;cout << static_cast<bool>(f) << '\n'; // truecout << *f << '\n'; // 23++f;cout << static_cast<bool>(f) << '\n'; // truecout << *f << '\n'; // 42++f;cout << static_cast<bool>(f) << '\n'; // false}
Iota
auto iota(long n) -> Generator<long> {for (auto i = 0u; i < n; ++i) {co_yield i;}}
Iota Usage
void test_iota() {for (auto i : iota(5)) {std::cout << i << '\n';}}
0
1
2
3
4Endless
auto from(long n) -> Generator<long> {while (true) {co_yield n++;}}
Generator<T> & co_yield
Real World Use Case
Test Scenario:
Write a parser for source code!
Design
- UTF8 encoded source
- ⇒ Unicode codepoints
- ⇒ Tokens
- ⇒ AST
UTF8 Decoder
auto utf8Decode(MemoryView)-> Generator<Codepoint>;
With Coroutine
auto utf8Decode(MemoryView view) -> Generator<CodePoint> {while (!view.empty()) {/* … */co_yield CodePoint{/* … */};}}
More Details
auto utf8Decode(MemoryView view) -> Generator<CodePoint> {auto take = [&]() -> CodePoint { return *view.begin++; };while (!view.empty()) {auto c0 = take();if ((c0 & 0x80) != 0x80)co_yield c0;else if ((c0 & 0xE0) == 0xC0) {if (view.empty()) co_return;auto c1 = take();if ((c1 & 0xC0) != 0x80) co_yield {};else co_yield { ((c0 & 0x1Fu) << 6) | (c1 & 0x3Fu) };}else { /* same for 3 & 4 bytes */ }}}
Tokenizer
auto tokenize(Generator<CodePoint>)-> Generator<Token>
Without Coroutine
auto tokenize(Generator<CodePoint> input) -> Generator<Token> {struct Scanner {Generator<CodePoint> input;TextPosition position{};CodePoint cp{};operator bool() const { /* ? */ }auto next() -> Token { /* … */ }}; return Scanner{std::move(input)};}
As Coroutine
auto tokenize(Generator<CodePoint> input) -> Generator<Token> {auto position = TextPosition{};auto next = [&] { return (++input) ? *input : CodePoint{}; };auto cp = next();while (cp) {auto token = [begin = position, end = &position](auto data) {return Token{TextRange{begin, end}, data};};if (cp == ':') {cp = next(); position.column++;co_yield token(Colon{});continue;}// …}}
Summary
- perfect fit for Genators
- ✔ generators
State Machines
Elevator
Elevator
States
struct Idle {};enum class Move { Up, Down };struct Broken {};using State = std::variant<Idle, Move, Broken>;
Events
struct Called { int toFloor; };struct FloorSensor { int floor; };struct AlarmPressed {};using Event = std::variant<Called, FloorSensor, AlarmPressed>;
Create the state machine
inline auto create() -> StateMachine<Event, State> {int current = 0;int target{};State state = Idle{};while (true) {Event event = co_yield state;// state * event => next_state}}
co_yield expression
// state * event => next_stateauto matchers = overloaded{[](auto, AlarmPressed) -> State {return Broken{};},[&](Idle, Called c) -> State {target = c.toFloor;return c.toFloor < current ? Move::Down : Move::Up;}}; state = std::visit(matchers, state, event);
Summary
- perfect for state machines
- ✔ generators
- ✔ state machines
async tasks
- non blocking IO
- prevent callback hell
- readable code
co_await
operator co_await() -> awaitable- customizable condition
- no result
- ✔ generators
- ✔ state machines
- ✔ async tasks
Coroutines
- "a new way of thinking"
- Clean Code™
- … Performance?
Performance
- clang
-std=c++17 -pedantic-stdlib=libc++ -fcoroutines-ts-O3
template<size_t N> auto fixedSum() {auto sum = 0u;for (auto v : iota(N + 1)) sum += v;return sum;}auto fixedTest() { return fixedSum<100>(); }
fixedTest():
mov eax, 5050 ; eax = 5050
ret ; return eaxauto simpleSum(size_t n) {
auto sum = 0u;
for (auto v = 0; v <= n; ++v) sum += v;
return sum;
}simpleSum(unsigned long): ; rdi = n
lea rax, [rdi - 1] ; rax = rdi - 1
imul rax, rdi ; rax *= rdi
shr rax, 1 ; rax /= 2
add edi, eax ; edi += eax
mov eax, edi ; eax = edi
ret ; return eaxauto dynSum(size_t n) {
auto sum = 0u;
for (auto v : iota(n + 1)) sum += v;
return sum;
}dynSum(unsigned long): ; rdi = n
add rdi, 1 ; rdi += 1
sete cl ; cl = rdi == 0
je .LBB1_1 ; if (rdi == 0) goto LBB1_1
xor eax, eax ; eax = 0
xor esi, esi ; esi = 0
.LBB1_4:
mov edx, esi ; edx = esi
add eax, esi ; eax += esi
mov esi, 0 ; esi = 0
test cl, 1 ; cl & 1 <=> 0
mov ecx, 0 ; ecx = 0
jne .LBB1_4 ; if (cl != 0) goto LBB1_4
add edx, 1 ; edx += 1
xor ecx, ecx ; ecx = 0
mov esi, edx ; esi = edx
cmp rdi, rdx ; rdi <=> rdx
ja .LBB1_4 ; if (rdi > rdx) goto LBB1_4
ret ; return eax
.LBB1_1:
xor eax, eax ; eax = 0
ret ; return eaxResults
- Godbolt Link
- use
noexcept - does not always work
- should get better!
Summary
- ✔ generators
- ✔ state machines
- ✔ async tasks
- ✔ performance
Advantages
- state machine in one function
- better locality of logic
- functional thinking
Disadvantages
- New
- Batteries not included
- Still only TS
Links
- Andreas Reischuck
- @arBmind
Work with us…
Give a Talk
& get a Dresden tour
Rebuild language project
Collaborate
Play with more features!
Play with Coroutine!
Photo Credits
co_await question_ready()