luncliff / coroutine

C++ 20 Coroutines in Action (Helpers + Test Code Examples)
https://luncliff.github.io/coroutine
Creative Commons Zero v1.0 Universal
483 stars 44 forks source link

Survey for common concepts #23

Closed luncliff closed 5 years ago

luncliff commented 5 years ago

Note

This is a long term project

Design/Apply some C++ Concepts

References

C++ Concepts

Repositories

Under Review ...

Related Issues

luncliff commented 5 years ago

Note

Trying concepts for co_await expression first.

$ 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++
)

Code

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.

Test

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>);
}

Update

Concept: Awaitable

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);
}
luncliff commented 5 years ago

Note

Coroutine Promise Requirement

Concept: Promise Requirement (Basic)

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 Requirement (Return Void)

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>);
}

Promise Requirement (Return Value)

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>);
}

Concept: Promise Requirement (Return)

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>);
}

Concept: Has Promise

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>);
}