Closed luncliff closed 5 years ago
Trying concepts for co_await
expression first.
gcc@8
)
__cpp_concepts
: 201507__cplusplus
: 201709$ gcc-8 --version
gcc-8 (Homebrew GCC 8.3.0_2) 8.3.0
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Used the minimal compile option
set_target_properties(test_suite
PROPERTIES
CXX_STANDARD 20
)
target_compile_options(test_suite
PRIVATE
-fconcepts
)
target_link_libraries(test_suite
PRIVATE
stdc++
)
Clang format makes the code ugly ... :(
template <typename P>
struct coroutine_handle{
// skip the detail ...
};
// member function constraints for `co_await` expression
template<typename T>
concept bool has_await_ready = requires(T a) {
{ a.await_ready() } -> bool;
};
template<typename T>
concept bool has_await_suspend = requires(T a, coroutine_handle<void> h) {
{ a.await_suspend(h) } -> void;
};
template<typename T, typename R>
concept bool has_await_resume = requires(T a) {
{ a.await_resume() } -> R;
};
// merge all constraints above ...
template<typename T, typename R = void>
concept bool is_awaitable =
has_await_ready<T> &&
has_await_suspend<T> &&
has_await_resume<T, R>;
But this makes too tiny concepts. In my opinion, they won't be meaningful. (since this concept is for expression).
template<typename T, typename R = void>
concept bool is_awaitable = requires(T a, coroutine_handle<void> h) {
{ a.await_ready() } -> bool;
{ a.await_suspend(h) } -> void;
{ a.await_resume() } -> R;
};
Also, it feels like the name is_awaitable
doesn't deliver enough meaning.
For now, simple constexpr check will be fine since GCC's coroutine branch is under work.
#include <catch/catch2.hpp>
TEST_CASE("concept constexpr")
{
constexpr auto b = is_awaitable<int>;
REQUIRE_FALSE(b);
}
Also, for access qualifier
// a type with functions for co_await
class public_await_functions {
public:
bool await_ready() const noexcept {
return true;
}
void await_suspend(coroutine_handle<void>) noexcept(false) {
}
void await_resume() noexcept(false) {
}
};
// hide functions using private inheritance
class private_await_functions : private public_await_functions {};
TEST_CASE("concept checks access qualifier")
{
REQUIRE(is_awaitable<public_await_functions>);
REQUIRE_FALSE(is_awaitable<private_await_functions>);
}
For now, the concept above will used with the name awaitable
.
template<typename T, typename R = void>
concept bool awaitable = requires(T a, coroutine_handle<void> h) {
{ a.await_ready() } -> bool;
{ a.await_suspend(h) } -> void;
{ a.await_resume() } -> R;
};
Receiving reference to a variable was successful.
TEST_CASE("reference") {
public_await_functions target{};
awaitable& a = target; // `is_awaitable` is awkward for this kind of usage...
REQUIRE(a.await_ready() == true);
}
Coroutine Promise Requirement
The final goal of this concept is to detect / apply assertions on coroutine functions' return types. The most basic concept's definition is clear enough to understand.
// requires suspend functions
template<typename P>
concept bool promise_requirement_basic = requires(P p) {
{ p.initial_suspend() } -> awaitable;
{ p.final_suspend() } -> awaitable;
{ p.unhandled_exception() } -> void;
};
Even though it's parameter type doesn't support return_void
and return_value
, which is necessary for the type to be used as a return type of the coroutine function, type check works well.
struct promise_basic {
auto initial_suspend() noexcept {
return suspend_never{};
}
auto final_suspend() noexcept {
return suspend_never{};
}
void unhandled_exception() noexcept {
}
};
TEST_CASE("requirement basic") {
REQUIRE_FALSE(promise_requirement_basic<void>);
REQUIRE(promise_requirement_basic<promise_basic>);
}
promise_return_void
, which is the extension of the concept promise_requirement_basic
, requires 2 more expressions.
template<typename T>
concept bool not_void = !std::is_same_v<T, void>;
template<promise_requirement_basic P>
concept bool promise_return_void = requires(P p) {
{ p.return_void() } -> void;
{ p.get_return_object() } -> not_void;
};
struct promise_void : public promise_basic{
void return_void() noexcept {
}
auto get_return_object() noexcept {
return this;
}
};
TEST_CASE("requirement void") {
REQUIRE_FALSE(promise_return_void<void>);
REQUIRE(promise_requirement_basic<promise_void>);
REQUIRE(promise_return_void<promise_void>);
}
TEST_CASE("not_void") {
REQUIRE_FALSE(not_void<void>);
REQUIRE(not_void<int>);
}
The extension promise_return_value
Is similar to promise_return_void
, but it uses additional template parameter since the parameter of return_value
function is unknown in this moment.
template<promise_requirement_basic P, not_void T>
concept bool promise_return_value = requires(P p, T&& v) {
{ p.return_value(std::move(v)) } -> void;
{ p.get_return_object() } -> not_void;
};
Notice the std::move
. I tried std::forward
, but it generated error.
template<not_void T>
struct promise_value : public promise_basic {
using value_type = T;
void return_value(T&& v) noexcept {
}
auto get_return_object() noexcept {
return this;
}
};
TEST_CASE("requirement value") {
// error: template argument 1 is invalid
// error: template constraint failure
// note: constraints not satisfied
// note: within 'template<class T> concept const bool not_void<T> [with T = void]'
// REQUIRE_FALSE(promise_return_value<promise_value<void>, void>);
// however, this constraint is passed
static_assert(!promise_return_value<void,void>);
using promise_type = promise_value<std::string>;
REQUIRE(promise_requirement_basic<promise_type>);
REQUIRE(promise_return_value<promise_type, promise_type::value_type>);
}
The conjunction of the 2 concept can be used to constraint promise_type
in a class
.
The code below will be refined with std::enable_if
if possible.
// todo: combination with `std::enable_if`
template<promise_requirement_basic P, not_void T>
concept bool promise_requirement_return =
promise_return_void<P> || promise_return_value<P,T>;
TEST_CASE("requirement return") {
using promise_1 = promise_void;
REQUIRE(promise_requirement_return<promise_1, void>);
using promise_2 = promise_value<std::string>;
REQUIRE(promise_requirement_return<promise_2, promise_2::value_type>);
}
template<typename T>
concept bool has_promise_type = requires(){
typename T::promise_type;
};
struct return_1 final {
using promise_type = promise_void;
public:
return_1(promise_type*) noexcept{}
};
TEST_CASE("has_promise_type") {
REQUIRE_FALSE(has_promise_type<int>);
REQUIRE(has_promise_type<return_1>);
}
Note
Design/Apply some C++ Concepts
References
C++ Concepts
Repositories
Under Review ...
Related Issues
33