Reputation: 21974
I am trying to define my own coroutine promise type, and I have the following declarations:
void return_void() requires std::same_as<T, void> {}
template<class X>
void return_value(X&& x) requires (!std::same_as<T, void>) {}
But, the compiler complains that I have defined both, despite the requires
declarations:
return.cpp: In function ‘Future<void> b()’:
return.cpp:103:40: error: the coroutine promise type ‘std::__n4861::__coroutine_traits_impl<Future<void>, void>::promise_type’ {aka ‘Future<void>::promise_type’} declares both ‘return_value’ and ‘return_void’
103 | __attribute__((noinline)) Future<void> b()
| ^
return.cpp:27:14: note: ‘return_void’ declared here
27 | void return_void() requires std::same_as<T, void> {}
| ^~~~~~~~~~~
return.cpp:30:14: note: ‘return_value’ first declared here
30 | void return_value(X&& x) requires (!std::same_as<T, void>) {}
| ^~~~~~~~~~~~
Isn't the point of the requires
feature to be able to express overloads like this without std::enable_if
trickery and inferred types?
Full minimal example:
#include <concepts>
#include <coroutine>
#include <exception>
#include <iostream>
#include <vector>
template<class T>
class Future
{
public:
class Awaiter;
class promise_type {
private:
std::exception_ptr _exception;
std::coroutine_handle<> _continuation;
friend class Awaiter;
public:
Future<T> get_return_object() { return {HandleT::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() requires std::same_as<T, void> {}
template<class X>
void return_value(X&& x) requires (!std::same_as<T, void>) {}
void unhandled_exception() {
_exception = std::current_exception();
}
};
operator bool() const
{
return _status == Status::Done || _status == Status::Exception;
}
struct Awaiter {
private:
Future<T>& future;
public:
explicit Awaiter(Future<T>& future)
: future(future)
{}
// this will check if the future is already done, e.g. if it
// was `co_await`ed previously
bool await_ready() const noexcept { return bool(future); }
// this coroutine is the one doing the `co_await`
std::coroutine_handle<> await_suspend(std::coroutine_handle<> handle) noexcept
{
// remember we want to resume back intot his caller later
future._handle.promise()._continuation = handle;
// For now let the coroutine associated with this future
// run, via "symmetric transfer", which takes the caller
// resume call off the stack and replaces it with this
// coroutine.
return future._handle;
}
void await_resume() const noexcept
{
}
};
auto operator co_await() {
return Awaiter(*this);
}
void resume() {
_handle.resume();
}
protected:
using HandleT = std::coroutine_handle<promise_type>;
Future(HandleT&& p)
: _handle(std::move(p))
{}
Future(const Future&) = delete;
Future& operator=(const Future&) = delete;
friend class Awaiter;
enum class Status {
Unfinished,
Done,
Exception,
Empty
} _status = Status::Unfinished;
HandleT _handle;
std::exception_ptr _exception = nullptr;
};
Future<void> b()
{
co_return;
}
Upvotes: 4
Views: 120
Reputation: 40023
It is normally overload resolution that “implements” requires
, in that it rejects candidates whose constraints are not satisfied and others whose constraints are subsumed by the remainder. Overload resolution can’t happen yet at this void
-or-not stage of analysis (at least for the return_value
case), so the requires
has no effect. Allowing this case and then just letting the usage (and constraints) sort it out would be exactly the P1713 that Barry mentioned.
Upvotes: 2