C++20 Coroutines What’s next?
Dawid Pilarski [email protected] blog.panicsoftware.com [email protected] Introduction Who am I?
Dawid Pilarski
• Senior Software Developer in TomTom • Member of the ISO/JTC1/SC22/WG21
• Member of the PKN KT (programming languages) • C++ blog writer
1 Agenda
Quick refresh about the coroutines.
Asynchronous RAII P1662R0
RVO for the co_await P1663R0
non tail call optimization P1713R0
2 Questions...
Please hold your questions till the end.
3 Quick refresh about the coroutines. What are the coroutines?
Subroutine Is a sequence of program instructions that perform a specific task, packaged as a unit. Function Is a subroutine Coroutine Is generalization of the function.
4 • suspended • resumed • created • destroyed
What are the coroutines?
Function can be:
• called • returned from
5 • resumed • created • destroyed
What are the coroutines?
Coroutine can be:
• called • returned from • suspended
5 • created • destroyed
What are the coroutines?
Coroutine can be:
• called • returned from • suspended • resumed
5 • destroyed
What are the coroutines?
Coroutine can be:
• called • returned from • suspended • resumed • created
5 What are the coroutines?
Coroutine can be:
• called • returned from • suspended • resumed • created • destroyed
5 Coroutine flowchart
Function’s flow: Coroutine flow:
6 • Developer must implement what keywords do.
• Implementation of promise_type (~6 functions) • Implementation of the co_await keyword (~3 functions)
How to implement custom coroutine types.
Creating custom coroutine type is not easy:
• C++ provides keywords only.
7 • Implementation of promise_type (~6 functions) • Implementation of the co_await keyword (~3 functions)
How to implement custom coroutine types.
Creating custom coroutine type is not easy:
• C++ provides keywords only. • Developer must implement what keywords do.
7 • Implementation of the co_await keyword (~3 functions)
How to implement custom coroutine types.
Creating custom coroutine type is not easy:
• C++ provides keywords only. • Developer must implement what keywords do.
This means:
• Implementation of promise_type (~6 functions)
7 How to implement custom coroutine types.
Creating custom coroutine type is not easy:
• C++ provides keywords only. • Developer must implement what keywords do.
This means:
• Implementation of promise_type (~6 functions) • Implementation of the co_await keyword (~3 functions)
7 How to implement custom coroutine types.
Creating custom coroutine type is not easy:
• C++ provides keywords only. • Developer must implement what keywords do.
This means:
• Implementation of promise_type (~6 functions) • Implementation of the co_await keyword (~3 functions)
You need to remember to implement on minimum 9 functions.
7 • Compiler knows the function is a coroutine by presence of keywords co_await, co_return, co_yield • If function is a coroutine it’s return type must support coroutines.
Coroutine declaration
// returned-type name arguments //|------| |------| |------| generator
• Whether the function is a coroutine depends on it’s definition.
8 • If function is a coroutine it’s return type must support coroutines.
Coroutine declaration
// returned-type name arguments //|------| |------| |------| generator
• Whether the function is a coroutine depends on it’s definition. • Compiler knows the function is a coroutine by presence of keywords co_await, co_return, co_yield
8 Coroutine declaration
// returned-type name arguments //|------| |------| |------| generator
• Whether the function is a coroutine depends on it’s definition. • Compiler knows the function is a coroutine by presence of keywords co_await, co_return, co_yield • If function is a coroutine it’s return type must support coroutines.
8 • member of the specialization of the coroutine_traits
Promise_type
Type supports coroutines if it has promise_type.
promise_type can be:
• member of the class
9 Promise_type
Type supports coroutines if it has promise_type.
promise_type can be:
• member of the class • member of the specialization of the coroutine_traits
9 • awaitable final_suspend(); • suspension at the end • return_type • how to create get_return_object(); return_type • void unhandled_exception(); • handling unhandled exception
Promise_type
Promise_type controls coroutine’s behavior.
• awaitable initial_suspend(); • suspension at the beginning
10 • return_type • how to create get_return_object(); return_type • void unhandled_exception(); • handling unhandled exception
Promise_type
Promise_type controls coroutine’s behavior.
• awaitable initial_suspend(); • suspension at the beginning • awaitable final_suspend(); • suspension at the end
10 • void unhandled_exception(); • handling unhandled exception
Promise_type
Promise_type controls coroutine’s behavior.
• awaitable initial_suspend(); • suspension at the beginning • awaitable final_suspend(); • suspension at the end • return_type • how to create get_return_object(); return_type
10 Promise_type
Promise_type controls coroutine’s behavior.
• awaitable initial_suspend(); • suspension at the beginning • awaitable final_suspend(); • suspension at the end • return_type • how to create get_return_object(); return_type • void unhandled_exception(); • handling unhandled exception
10 • co_return; • p.return_void(); • co_yield V; • co_await p.yield_value();
Keywords and promise_type
Promise_type is also responsible for keyword’s actions:
• co_return V; • p.return_value(V);
11 • co_yield V; • co_await p.yield_value();
Keywords and promise_type
Promise_type is also responsible for keyword’s actions:
• co_return V; • p.return_value(V); • co_return; • p.return_void();
11 Keywords and promise_type
Promise_type is also responsible for keyword’s actions:
• co_return V; • p.return_value(V); • co_return; • p.return_void(); • co_yield V; • co_await p.yield_value();
11 • void • bool • another coroutine_handle
• bool await_ready() • await_suspend(coroutine_handle
) returning
• T await_resume()
• have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself):
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties:
12 • void • bool • another coroutine_handle
• bool await_ready() • await_suspend(coroutine_handle
) returning
• T await_resume()
• have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself):
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or
12 • void • bool • another coroutine_handle
• bool await_ready() • await_suspend(coroutine_handle
) returning
• T await_resume()
• implement 3 functions (be awaiter itself):
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or
12 • void • bool • another coroutine_handle
• bool await_ready() • await_suspend(coroutine_handle
) returning
• T await_resume()
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself):
12 • void • bool • another coroutine_handle
• await_suspend(coroutine_handle
) returning
• T await_resume()
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself): • bool await_ready()
12 • void • bool • another coroutine_handle • T await_resume()
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself): • bool await_ready() • await_suspend(coroutine_handle
) returning
12 • bool • another coroutine_handle • T await_resume()
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself): • bool await_ready() • await_suspend(coroutine_handle
) returning • void
12 • another coroutine_handle • T await_resume()
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself): • bool await_ready() • await_suspend(coroutine_handle
) returning • void • bool
12 • T await_resume()
co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself): • bool await_ready() • await_suspend(coroutine_handle
) returning • void • bool • another coroutine_handle
12 co_await
In order to support co_await expressions, the argument (awaitable):
• promise_type.await_transform(A) (if exists) must produce object which type has below properties: • have awaiter operator co_await defined, or • have global awaiter operator co_await(A) support, or • implement 3 functions (be awaiter itself): • bool await_ready() • await_suspend(coroutine_handle
) returning • void • bool • another coroutine_handle • T await_resume()
12 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 Resuming coroutine with coroutine_handle
template<> struct coroutine_handle
constexpr void* address() const noexcept; constexpr static coroutine_handle from_address(void* addr);
constexpr explicit operator bool() const noexcept; bool done() const;
void operator()() const; void resume() const;
void destroy() const; //...
13 And how do I get the coroutine_handle object?
coroutine_handles are specialized for promise_type
template
constexpr static coroutine_handle from_address(void* addr);
Promise& promise() const; };
14 And how do I get the coroutine_handle object?
coroutine_handles are specialized for promise_type
template
constexpr static coroutine_handle from_address(void* addr);
Promise& promise() const; };
14 And how do I get the coroutine_handle object?
coroutine_handles are specialized for promise_type
template
constexpr static coroutine_handle from_address(void* addr);
Promise& promise() const; };
14 And how do I get the coroutine_handle object?
coroutine_handles are specialized for promise_type
template
constexpr static coroutine_handle from_address(void* addr);
Promise& promise() const; };
14 And how do I get the coroutine_handle object?
coroutine_handles are specialized for promise_type
template
constexpr static coroutine_handle from_address(void* addr);
Promise& promise() const; };
14 Why is that useful?
We can:
• hide asynchronous code • hide state management
15 Asynchronous RAII P1662R0 Asynchronous RAII
RAII - Resource Acquisition Is Initialization.
16 RAII vs. coroutines
How do coroutines differ?
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
No RAII to close the file!
17 RAII vs. coroutines
How do coroutines differ?
task
auto opened_file= co_await async_open(path); auto content= co_await async_read(opened_file); co_await async_close(opened_file);
co_return content; }
No RAII to close the file! Possible leak when async_read throws
17 It’s not only about proper error handling
Consider following scenario:
generator
18 It’s not only about proper error handling
Consider following scenario:
generator
18 It’s not only about proper error handling
Consider following scenario:
generator
18 It’s not only about proper error handling
Consider following scenario:
generator
18 It’s not only about proper error handling
Consider following scenario:
generator
18 • at the break; we are destroying coroutine • not all lines from file might be consumed • proper cleanup needs to be performed anyway on coroutine_handle::destroy()
generator example usage
for(const auto& line : lines("myfile.txt")){ if(starts_with(line, "string I am looking for")) break; }
19 • not all lines from file might be consumed • proper cleanup needs to be performed anyway on coroutine_handle::destroy()
generator example usage
for(const auto& line : lines("myfile.txt")){ if(starts_with(line, "string I am looking for")) break; }
• at the break; we are destroying coroutine
19 • proper cleanup needs to be performed anyway on coroutine_handle::destroy()
generator example usage
for(const auto& line : lines("myfile.txt")){ if(starts_with(line, "string I am looking for")) break; }
• at the break; we are destroying coroutine • not all lines from file might be consumed
19 generator example usage
for(const auto& line : lines("myfile.txt")){ if(starts_with(line, "string I am looking for")) break; }
• at the break; we are destroying coroutine • not all lines from file might be consumed • proper cleanup needs to be performed anyway on coroutine_handle::destroy()
19 on destroy on loop finished
∼ifstream()
No issue for the given synchronous coroutine
generator
20 on destroy on loop finished
No issue for the given synchronous coroutine
generator
20 on destroy
No issue for the given synchronous coroutine
generator
20 No issue for the given synchronous coroutine
generator
20 asynchronous generators
async_generator
co_await async_close(opened_file); }
21 asynchronous generators
async_generator
co_await async_close(opened_file); }
21 asynchronous generators
async_generator
co_await async_close(opened_file); }
21 asynchronous generators
async_generator
co_await async_close(opened_file); }
21 on early destroy - no cleanup
on loop finished
asynchronous generators
async_generator
co_await async_close(opened_file); } cleanup
22 on early destroy - no cleanup
asynchronous generators
async_generator
co_await async_close(opened_file); } on loop finished cleanup
22 asynchronous generators
async_generator
co_await async_close(opened_file); } on loop finished cleanup
22 Current solution to the problem
async_generator
co_await async_close(opened_file); }
23 Current solution to the problem
async_generator
co_await async_close(opened_file); }
23 Current solution to the problem
async_generator
co_await async_close(opened_file); }
23 Current solution to the problem
async_generator
co_await async_close(opened_file); }
23 Current solution to the problem
async_generator
co_await async_close(opened_file); }
23 • ∼operator co_await gets co-awaited at the end of the scope • instead of destroy() you will invoke set_done()
Proposed coroutines improvement
• create special function in the awaiter : ∼operator co_await
24 • instead of destroy() you will invoke set_done()
Proposed coroutines improvement
• create special function in the awaiter : ∼operator co_await • ∼operator co_await gets co-awaited at the end of the scope
24 Proposed coroutines improvement
• create special function in the awaiter : ∼operator co_await • ∼operator co_await gets co-awaited at the end of the scope • instead of destroy() you will invoke set_done()
24 Example
async_generator
// async_close happens as a part of clean-up }
25 Example
async_generator
// async_close happens as a part of clean-up }
25 • coroutine bodies • coroutine type, because we cannot simply destroy the coroutine • In the space of asynchronous operations we have got no RAII idiom • With adoption of the proposal above is solved
Summary
• Currently it’s difficult to correctly implement asynchronous generators
26 • coroutine type, because we cannot simply destroy the coroutine • In the space of asynchronous operations we have got no RAII idiom • With adoption of the proposal above is solved
Summary
• Currently it’s difficult to correctly implement asynchronous generators • coroutine bodies
26 • In the space of asynchronous operations we have got no RAII idiom • With adoption of the proposal above is solved
Summary
• Currently it’s difficult to correctly implement asynchronous generators • coroutine bodies • coroutine type, because we cannot simply destroy the coroutine
26 • With adoption of the proposal above is solved
Summary
• Currently it’s difficult to correctly implement asynchronous generators • coroutine bodies • coroutine type, because we cannot simply destroy the coroutine • In the space of asynchronous operations we have got no RAII idiom
26 Summary
• Currently it’s difficult to correctly implement asynchronous generators • coroutine bodies • coroutine type, because we cannot simply destroy the coroutine • In the space of asynchronous operations we have got no RAII idiom • With adoption of the proposal above is solved
26 RVO for the co_await P1663R0 For example:
std::vector
// no copy or move construction // invoked auto_= foo();
What is RVO?
RVO - Return Value Optimization.
Allows to avoid unnecessary copy or move construction of the values returned from the function.
27 What is RVO?
RVO - Return Value Optimization.
Allows to avoid unnecessary copy or move construction of the values returned from the function. For example:
std::vector
// no copy or move construction // invoked auto_= foo();
27 RVO on regular functions
regular function transformed by compiler into: std::vector
28 1. On await_suspend coroutine gets executed 2. On await_resume result is returned
Why RVO is not possible with co_await
expression transformed by compiler into: co_await task; { auto&& awaiter= transform(task); if(!awaiter.await_ready()){
29 2. On await_resume result is returned
Why RVO is not possible with co_await
expression transformed by compiler into: co_await task; { auto&& awaiter= transform(task); 1. On if(!awaiter.await_ready()){ await_suspend
29 Why RVO is not possible with co_await
expression transformed by compiler into: co_await task; { auto&& awaiter= transform(task); 1. On if(!awaiter.await_ready()){ await_suspend
29 2. await_suspend will create return result 3. Remove await_ready function.
How to resolve the issue
1. Remove { await_resume auto&& awaiter= transform(task); function. if(!awaiter.await_ready()){
30 3. Remove await_ready function.
How to resolve the issue
1. Remove { await_resume auto&& awaiter= transform(task); function. if(!awaiter.await_ready()){ 2. await_suspend
30 How to resolve the issue
1. Remove { await_resume auto&& awaiter= transform(event); function. if(!awaiter.await_ready()){ 2. await_suspend
30 How to resolve the issue
1. Remove { await_resume auto&& awaiter= transform(event); function.
30 • set_value_from(std::in_place_construct
On coroutine resumption the compiler will generate code to check whether the exception was saved with set_exception and will rethrow it when needed.
Extensions to the coroutine_handle
Two additional functions in the coroutine_handle are needed.
• set_value(T)
31 • set_exception(exception_ptr)
On coroutine resumption the compiler will generate code to check whether the exception was saved with set_exception and will rethrow it when needed.
Extensions to the coroutine_handle
Two additional functions in the coroutine_handle are needed.
• set_value(T) • set_value_from(std::in_place_construct
31 On coroutine resumption the compiler will generate code to check whether the exception was saved with set_exception and will rethrow it when needed.
Extensions to the coroutine_handle
Two additional functions in the coroutine_handle are needed.
• set_value(T) • set_value_from(std::in_place_construct
31 Extensions to the coroutine_handle
Two additional functions in the coroutine_handle are needed.
• set_value(T) • set_value_from(std::in_place_construct
On coroutine resumption the compiler will generate code to check whether the exception was saved with set_exception and will rethrow it when needed.
31 Example of the yield_value
template
template
template
}; 32 Example of the yield_value
template
template
template
}; 32 Example of the yield_value
template
template
template
}; 32 Example of the yield_value
template
template
template
}; 32 Example of the yield_value
template
template
template
}; 32 Example of the yield_value
template
template
template
}; 32 How do compiler know the result of the co_await?
With removal of the await_resume the compiler no longer knows about the co_await returned type.
We will need to guide the compiler. The proposal P1663R0 proposes to add member await_result_type to the Awaiter.
33 • no temporary variable created • allocated coroutine state is smaller
• removing await_ready makes • RVO optimizations co_await always suspend the coroutine (even if not needed) • a need to support RVO manually (with the help of construct_in_place)
RVO summary
pros cons • very simplified awaiter concept
34 • removing await_ready makes co_await always suspend the • no temporary variable coroutine (even if not needed) created • a need to support RVO • allocated coroutine state is manually (with the help of smaller construct_in_place)
RVO summary
pros cons • very simplified awaiter concept • RVO optimizations
34 • removing await_ready makes co_await always suspend the coroutine (even if not needed) • a need to support RVO • allocated coroutine state is manually (with the help of smaller construct_in_place)
RVO summary
pros cons • very simplified awaiter concept • RVO optimizations • no temporary variable created
34 • removing await_ready makes co_await always suspend the coroutine (even if not needed) • a need to support RVO manually (with the help of construct_in_place)
RVO summary
pros cons • very simplified awaiter concept • RVO optimizations • no temporary variable created • allocated coroutine state is smaller
34 • a need to support RVO manually (with the help of construct_in_place)
RVO summary
pros cons • very simplified awaiter concept • removing await_ready makes • RVO optimizations co_await always suspend the • no temporary variable coroutine (even if not needed) created • allocated coroutine state is smaller
34 RVO summary
pros cons • very simplified awaiter concept • removing await_ready makes • RVO optimizations co_await always suspend the • no temporary variable coroutine (even if not needed) created • a need to support RVO • allocated coroutine state is manually (with the help of smaller construct_in_place)
34 non tail call optimization P1713R0 Why would we even need that?
return value [and|or] return void
right now it’s not possible to implement both in the same scope.
• return_value(T) • return_void()
35 return value [and|or] return void
right now it’s not possible to implement both in the same scope.
• return_value(T) • return_void() Why would we even need that?
35 return_value(42);
return_void()
Motivating example: implementation simplifications
task
task
36 return_void()
Motivating example: implementation simplifications
task
task
36 Motivating example: implementation simplifications
task
task
36 But that’s not the way it works.
How implementors would like to implement it
template
template
37 But that’s not the way it works.
How implementors would like to implement it
template
template
37 How implementors would like to implement it
template
template
37 template <> struct task
How implementors have to implement it?
template
38 How implementors have to implement it?
template
38 task
task
Tail coroutine call
What are tail coroutine calls?
39 task
task
Tail coroutine call
What are tail coroutine calls?
task
task
39 Tail coroutine call
What are tail coroutine calls? Find a difference in the pictures
task
task
39 Tail coroutine call
What are tail coroutine calls?
task
task
tail call / no tail call
39 co_await co_await co_await
bar bar2 foo2 co_return co_return co_return
destroy destroy destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
How does regular/tail call work?
First, how does regular call work?
foo
40 co_await co_await
bar bar2 foo2 co_return co_return co_return
destroy destroy destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
How does regular/tail call work?
First, how does regular call work?
co_await
foo bar
40 co_await
bar bar2 foo2 co_return co_return co_return
destroy destroy destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
How does regular/tail call work?
First, how does regular call work?
co_await co_await
foo bar bar2
40 bar bar2 foo2 co_return co_return co_return
destroy destroy destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
How does regular/tail call work?
First, how does regular call work?
co_await co_await co_await
foo bar bar2 foo2
40 bar bar2 co_return co_return
destroy destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
How does regular/tail call work?
First, how does regular call work?
co_await co_await co_await
foo bar bar2 foo2 co_return
destroy
40 bar co_return
destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
How does regular/tail call work?
First, how does regular call work?
co_await co_await co_await
foo bar bar2 foo2 co_return co_return
destroy destroy
40 How does regular/tail call work?
First, how does regular call work?
co_await co_await co_await
foo bar bar2 foo2 co_return co_return co_return
destroy destroy destroy
Conclusion:
• At peak 4 coroutine frames had to be allocated • Only after returning to the caller coroutine, called one can be destroyed
40 create resume resume
foo bar bar2 foo2
destroy destroy destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one.
foo
41 resume resume
foo bar bar2 foo2
destroy destroy destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. create
foo bar
41 resume resume
bar bar2 foo2
destroy destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. create
foo bar
destroy
41 resume resume
bar bar2 foo2
destroy destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume
foo bar
destroy
41 resume
bar bar2 foo2
destroy destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume create
foo bar bar2
destroy
41 resume
bar2 foo2
destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume create
foo bar bar2
destroy destroy
41 resume
bar2 foo2
destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume resume
foo bar bar2
destroy destroy
41 bar2
destroy
return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume resume create
foo bar bar2 foo2
destroy destroy
41 return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume resume create
foo bar bar2 foo2
destroy destroy destroy
41 return
How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume resume resume
foo bar bar2 foo2
destroy destroy destroy
41 How does non tail-call work?
In case of non tail-call we first destroy the coroutine and then call another one. resume resume resume
foo bar bar2 foo2
destroy destroy destroy
return
At most 2 frames were allocated.
41 Tail call is implementable.
Tail call is implementable. But only for non-void co_returning types.
42 p.return_void();
p.return_value(display_text(content))
Why it’s not implementable for task
task
task
43 p.return_value(display_text(content))
Why it’s not implementable for task
task
task
43 p.return_value(display_text(content))
Why it’s not implementable for task
task
task
43 Why it’s not implementable for task
task
task
43 Both cannot apear in the same scope!
Why it’s not implementable for task
template <> struct task
void return_void(){}
void return_value(task
44 Both cannot apear in the same scope!
Why it’s not implementable for task
template <> struct task
void return_void(){}
void return_value(task
44 Why it’s not implementable for task
template <> struct task
void return_void(){} Both cannot void return_value(task
44 • make it possible for some types to support non tail coroutines calls
Summary
After accepting this change we will able to:
• simplify implementations of promise_types for some cases.
45 Summary
After accepting this change we will able to:
• simplify implementations of promise_types for some cases. • make it possible for some types to support non tail coroutines calls
45 Thank you for your attention! Special thank you! goes to:
• Gor Nishanov • Lewis Baker
for creating C++ coroutines
46 Summary Bibliography and further reading
• James McNellis - • Lewis Baker’s Assymetric "Introduction to the C++ transfer blog Coroutines" • newest C++ draft • Gor Nishanov - any video • My blog - about the coroutines blog.panicsoftware.com • Toby Allsopp - "Coroutines: • coroutines proposals what can’t they do?"
47 Questions?
Questions?
blog blog.panicsoftware.com private mail [email protected] corporate mail [email protected] this https://github.com/dawidpilarski/CodeDive-coroutines
48