Key components:
- Return type
- Promise type
- Awaitable type
- std::coroutine_handle<>
Clang Attributes:
task<int> coro(const int& a) { co_return a + 1; }
task<int> dangling_refs(int a) {
// `coro` captures reference to a temporary. `foo` would now contain a dangling reference to `a`.
auto foo = coro(1);
// `coro` captures reference to local variable `a` which is destroyed after the return.
return coro(a);
}
template <typename T> struct [[clang::coro_return_type, clang::coro_lifetimebound]] Task {
using promise_type = some_promise_type;
};
Task<int> coro(const int& a) { co_return a + 1; }
[[clang::coro_wrapper]] Task<int> coro_wrapper(const int& a, const int& b) {
return a > b ? coro(a) : coro(b);
}
Task<int> temporary_reference() {
auto foo = coro(1); // warning: capturing reference to a temporary which would die after the expression.
int a = 1;
auto bar = coro_wrapper(a, 0); // warning: `b` captures reference to a temporary.
co_return co_await coro(1); // fine.
}
[[clang::coro_wrapper]] Task<int> stack_reference(int a) {
return coro(a); // warning: returning address of stack variable `a`.
}
Important performance to avoid temporary object being created during pass by value
but resides in the register
https://vsdmars.blogspot.com/2021/01/c-trivialabi-and-borrow-ownership.html
but resides in the register
https://vsdmars.blogspot.com/2021/01/c-trivialabi-and-borrow-ownership.html
Attribute trivial_abi has no effect in the following cases:
- The class directly declares a virtual base or virtual methods.
- Copy constructors and move constructors of the class are all deleted.
- The class has a base class that is non-trivial for the purposes of calls.
- The class has a non-static data member whose type is non-trivial for the purposes of calls, which includes:
- classes that are non-trivial for the purposes of calls
- __weak-qualified types in Objective-C++
- arrays of any of the above
Concept:
`co_yield value` is a syntax sugar for `co_await awaitable`.
thus value is stored in promise_type instance directly instead of having to go through
`Awaitable{value}` and store the value into promise_type instance through `Awaitable::await_suspend(handler)`
Caller could retrieve the value through
`ReturnType::handler_::promise.value_;`
std::suspend_always {
// always suspend.
bool await_ready() { return false;}
// noop
void await_suspend()
// noop, the value is stored in promise_type.value_.
void await_resume()
} // https://en.cppreference.com/w/cpp/coroutine/suspend_always.html
std::suspend_never {
// never suspend.
bool await_ready() { return true;}
// noop
void await_suspend()
// noop, the value is stored in promise_type.value_.
void await_resume()
}; // https://en.cppreference.com/w/cpp/coroutine/suspend_never.html
// Controls caller's behavior. Thus await_suspend is passing in caller's
// coroutine handler. However, the Awaitable instance is created by the
// callee, which it could store the callee's coroutine handler.
strict Awaitable {
T value_;
bool await_ready() { return false;}
void await_suspend(std::coroutine_handle<promise_type> handler) {
// could store the result into handler.promise().RESULT.
// or store the value_ into handler.promise().RESULT.
// Handler can be passed into new thread.
// When handler calls .resume(), it starts execute from where previous
// suspended.
// These await_* functions controlls how the caller, i.e. not the callee that
// provides this Awaitable but the caller, behave.
// if returns false, indicates the caller will continue and
// await_resume is called immidiately and the result is returned to the caller.
// if returns true, the caller is suspended and the callee only resume
// if callee's handler::resume() is called. otherwise, callee and caller both
// are suspended and return back to main()/root caller function(i.e. the caller function
// that is not a coroutine.
}
// handler.resume() from Awaitable suspend comes here.
// can return value to the caller inside the coroutine.
// the value can be obtained from handler.promise().RESULT.
void await_resume();
T await_resume() {return T{}; };
Awaitable(T value) : value_(value) {}
};
struct ReturnType {
struct promise_type {
T value_;
promise_type(T...); // optional
ReturnType get_return_object() {
return {};
// or
return {coroutine::from_promise(*this)};
};
std::suspend_always initial_suspend() {}
template<std::convertible_to<T> From> // C++20 concept
std::suspend_always yield_value(From&& from) {
value_ = std::forward<From>(from); // caching the result in promise
return {};
}
// above start; below shutdown
void return_value(T/T&&/const T&); // store value T inside the ReturnType, caller retrieve the value from ReturnType.
void return_void();
void unhandled_exception();
// 1) if returns suspend_always; the caller has the chance to control how coroutine is going
// to be clean-up, e.g. release lock, release resources etc. and then call .destroy()
// manually, usually in the ReturnType's destructor. After .destroy() is called,
// std::coroutine_handle::operator bool continue to return true, this is due
// to coroutine_handle does initial with a coroutine(that it points to).
// And If std::coroutine_handle::destroy()
// is called, std::coroutine_handle::done() will have undefined behavior and should not be called.
// 2) if returns suspend_never; the coroutine is destroyed immediately, and calling
// std::coroutine_handle::done() is UB since frame is destroyed.
// 3) if suspend_always, the coroutine is suspended and std::coroutine_handle::done() is True.
std::suspend_always final_suspend() noexcept;
};
ReturnType(std::coroutine_handle<promise_type> handler) : handler_(handler) {}
ReturnType() = default;
};
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.