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'; // true
cout << *f << '\n'; // 23
++f;
cout << static_cast<bool>(f) << '\n'; // true
cout << *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
4
Endless
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_state
auto 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 eax
auto 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 eax
auto 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 eax
Results
- 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()