From 867ab38b8e2706313ae76945a486d126b094ef27 Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Sat, 24 Nov 2018 22:48:16 +0100 Subject: [PATCH] Rework the expected_trait * Add tests for the new expected public interface --- include/continuable/continuable-base.hpp | 9 +- include/continuable/continuable-expected.hpp | 125 +++++++++++++++--- include/continuable/detail/core/base.hpp | 5 +- .../continuable/detail/other/coroutines.hpp | 4 +- .../detail/utility/expected-traits.hpp | 42 ++---- .../detail/utility/flat-variant.hpp | 16 ++- test/unit-test/CMakeLists.txt | 1 + .../multi/test-continuable-base-multipath.cpp | 30 +++++ .../single/test-continuable-expected.cpp | 83 ++++++++---- 9 files changed, 232 insertions(+), 83 deletions(-) create mode 100644 test/unit-test/multi/test-continuable-base-multipath.cpp diff --git a/include/continuable/continuable-base.hpp b/include/continuable/continuable-base.hpp index b5292c0..b5b4d06 100644 --- a/include/continuable/continuable-base.hpp +++ b/include/continuable/continuable-base.hpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -898,15 +899,15 @@ constexpr auto make_exceptional_continuable(Exception&& exception) { template auto recover(Args&&... args) { - // TODO + return make_expected(std::forward(args)...); } inline auto rethrow(exception_t exception) { - // TODO + return make_exceptional_expected(std::move(exception)); } -inline constexpr auto cancel() { - // TODO +inline auto cancel() { + return make_none_expected(); } /// \} } // namespace cti diff --git a/include/continuable/continuable-expected.hpp b/include/continuable/continuable-expected.hpp index 98fbef8..4f21fb6 100644 --- a/include/continuable/continuable-expected.hpp +++ b/include/continuable/continuable-expected.hpp @@ -34,14 +34,71 @@ #include #include #include +#include #include +#include namespace cti { +/// A class which is convertible to any expected and that definitly holds no +/// value so the real expected gets invalidated when +/// this object is passed to it +struct empty_expected { + empty_expected() = default; + empty_expected(empty_expected const&) = default; + empty_expected(empty_expected&&) = default; + empty_expected& operator=(empty_expected const&) = default; + empty_expected& operator=(empty_expected&&) = default; + ~empty_expected() = default; +}; + +/// A class which is convertible to any expected and that definitly holds +/// an exception which is then passed to the converted expected object. +class exceptional_expected { + exception_t exception_; + +public: + exceptional_expected() = delete; + exceptional_expected(exceptional_expected const&) = default; + exceptional_expected(exceptional_expected&&) = default; + exceptional_expected& operator=(exceptional_expected const&) = default; + exceptional_expected& operator=(exceptional_expected&&) = default; + ~exceptional_expected() = default; + + explicit exceptional_expected(exception_t exception) + // NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg) + : exception_(std::move(exception)) { + } + + exceptional_expected& operator=(exception_t exception) { + // NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg) + exception_ = std::move(exception); + return *this; + } + + void set_exception(exception_t exception) { + // NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg) + exception_ = std::move(exception); + } + + exception_t& get_exception() & noexcept { + return exception_; + } + exception_t const& get_exception() const& noexcept { + return exception_; + } + exception_t&& get_exception() && noexcept { + return std::move(exception_); + } +}; + /// A class similar to the one in the expected proposal, /// however it's capable of carrying an exception_t. -template +template class expected { - detail::container::flat_variant variant_; + using trait = detail::expected_trait; + using value_t = typename trait::value_t; + + detail::container::flat_variant variant_; public: explicit expected() = default; @@ -51,32 +108,39 @@ public: expected& operator=(expected&&) = default; ~expected() = default; - explicit expected(T value) : variant_(std::move(value)) { + explicit expected(T... values) : variant_(trait::wrap(std::move(values)...)) { } explicit expected(exception_t exception) : variant_(std::move(exception)) { } + explicit expected(empty_expected){}; + explicit expected(exceptional_expected exceptional_expected) + : variant_(std::move(exceptional_expected.get_exception())) { + } - expected& operator=(T value) { - variant_ = std::move(value); + expected& operator=(empty_expected) { + set_empty(); return *this; } - expected& operator=(exception_t exception) { - variant_ = std::move(exception); + expected& operator=(exceptional_expected exceptional_expected) { + set_exception(std::move(exceptional_expected.get_exception())); return *this; } - void set_value(T value) { - variant_ = std::move(value); + void set_empty() { + variant_.set_empty(); + } + void set_value(T... values) { + variant_ = std::move(values...); } void set_exception(exception_t exception) { variant_ = std::move(exception); } - bool is_empty() { + bool is_empty() const noexcept { return variant_.is_empty(); } bool is_value() const noexcept { - return variant_.template is(); + return variant_.template is(); } bool is_exception() const noexcept { return variant_.template is(); @@ -86,26 +150,49 @@ public: return is_value(); } - T& get_value() noexcept { - return variant_.template cast(); + value_t& get_value() & noexcept { + return variant_.template cast(); } - T const& get_value() const noexcept { - return variant_.template cast(); + value_t const& get_value() const& noexcept { + return variant_.template cast(); } - exception_t& get_exception() noexcept { + value_t&& get_value() && noexcept { + return std::move(variant_).template cast(); + } + exception_t& get_exception() & noexcept { return variant_.template cast(); } - exception_t const& get_exception() const noexcept { + exception_t const& get_exception() const& noexcept { return variant_.template cast(); } + exception_t&& get_exception() && noexcept { + return std::move(variant_).template cast(); + } - T& operator*() noexcept { + value_t& operator*() & noexcept { return get_value(); } - T const& operator*() const noexcept { + value_t const& operator*() const& noexcept { return get_value(); } + value_t&& operator*() && noexcept { + return std::move(*this).get_value(); + } }; + +template +constexpr auto make_expected(T&&... values) { + return expected...>(std::forward(values)); +} + +inline auto make_exceptional_expected(exception_t exception) { + // NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg) + return exceptional_expected(std::move(exception)); +} + +inline auto make_empty_expected() { + return empty_expected{}; +} } // namespace cti #endif // CONTINUABLE_EXPECTED_HPP_INCLUDED diff --git a/include/continuable/detail/core/base.hpp b/include/continuable/detail/core/base.hpp index deeeb13..d43c7d7 100644 --- a/include/continuable/detail/core/base.hpp +++ b/include/continuable/detail/core/base.hpp @@ -342,7 +342,7 @@ inline auto make_error_invoker( std::integral_constant) noexcept { return [](auto&& callback, exception_t&& error) { // Errors are not partial invoked - // NOLINTNEXTLINE(hicpp-move-const-arg) + // NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg) std::forward(callback)(std::move(error)); }; } @@ -352,7 +352,8 @@ inline auto make_error_invoker( // Errors are not partial invoked std::forward(callback)( exception_arg_t{}, - std::move(error)); // NOLINT(hicpp-move-const-arg) + // NOLINTNEXTLINE(hicpp-move-const-arg, performance-move-const-arg) + std::move(error)); }; } diff --git a/include/continuable/detail/other/coroutines.hpp b/include/continuable/detail/other/coroutines.hpp index c07e7f2..2027ecf 100644 --- a/include/continuable/detail/other/coroutines.hpp +++ b/include/continuable/detail/other/coroutines.hpp @@ -58,7 +58,9 @@ using std::experimental::coroutine_handle; /// for waiting on a continuable in a stackless coroutine. template class awaitable { - using trait_t = container::expected_result_trait_t; + using hint_t = decltype(hints::hint_of(traits::identify{})); + using trait_t = expected_trait; + using value_t = expected_trait; /// The continuable which is invoked upon suspension Continuable continuable_; diff --git a/include/continuable/detail/utility/expected-traits.hpp b/include/continuable/detail/utility/expected-traits.hpp index 2da91bf..9263950 100644 --- a/include/continuable/detail/utility/expected-traits.hpp +++ b/include/continuable/detail/utility/expected-traits.hpp @@ -31,63 +31,41 @@ #ifndef CONTINUABLE_DETAIL_EXPECTED_TRAITS_HPP_INCLUDED #define CONTINUABLE_DETAIL_EXPECTED_TRAITS_HPP_INCLUDED +#include #include #include -#include #include namespace cti { namespace detail { -namespace container { -namespace detail { -struct void_guard_tag {}; - -template -struct expected_result_trait; +template +struct expected_trait; template <> -struct expected_result_trait> { - using expected_type = expected; +struct expected_trait> { + struct value_t {}; - static constexpr void_guard_tag wrap() noexcept { + static constexpr value_t wrap() noexcept { return {}; } - static void unwrap(expected_type&& e) { - assert(e.is_value()); - (void)e; - } }; template -struct expected_result_trait> { - using expected_type = expected; +struct expected_trait { + using value_t = T; static auto wrap(T arg) { return std::move(arg); } - static auto unwrap(expected_type&& e) { - assert(e.is_value()); - return std::move(e.get_value()); - } }; template -struct expected_result_trait> { - using expected_type = expected>; +struct expected_trait { + using value_t = std::tuple; static auto wrap(First first, Second second, Rest... rest) { return std::make_tuple(std::move(first), std::move(second), std::move(rest)...); } - static auto unwrap(expected_type&& e) { - assert(e.is_value()); - return std::move(e.get_value()); - } }; } // namespace detail - -template -using expected_result_trait_t = detail::expected_result_trait{}))>; -} // namespace container -} // namespace detail } // namespace cti #endif // CONTINUABLE_DETAIL_EXPECTED_TRAITS_HPP_INCLUDED diff --git a/include/continuable/detail/utility/flat-variant.hpp b/include/continuable/detail/utility/flat-variant.hpp index 9ea9549..24987d6 100644 --- a/include/continuable/detail/utility/flat-variant.hpp +++ b/include/continuable/detail/utility/flat-variant.hpp @@ -267,6 +267,11 @@ public: return *this; } + void set_empty() { + weak_destroy(); + set_slot(detail::empty_slot::value); + } + template , T...>::value> bool is() const noexcept { @@ -282,17 +287,24 @@ public: } template - V& cast() noexcept { + V& cast() & noexcept { assert(is_slot(traits::index_of_t, T...>::value)); return *reinterpret_cast*>(&this->storage_); } template - V const& cast() const noexcept { + V const& cast() const& noexcept { assert(is_slot(traits::index_of_t, T...>::value)); return *reinterpret_cast const*>(&this->storage_); } + template + V&& cast() && noexcept { + assert(is_slot(traits::index_of_t, T...>::value)); + auto& value = *reinterpret_cast const*>(&this->storage_); + return std::move(value); + } + private: template static void visit_dispatch(flat_variant* me, V&& visitor) { diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt index d00670f..8d357d5 100644 --- a/test/unit-test/CMakeLists.txt +++ b/test/unit-test/CMakeLists.txt @@ -51,6 +51,7 @@ foreach(STEP RANGE ${STEP_RANGE}) ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-base-destruct.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-base-errors.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-base-partial.cpp + ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-base-multipath.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-connection-all-seq-ag-1.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-connection-all-seq-ag-2.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-connection-all-seq-op.cpp diff --git a/test/unit-test/multi/test-continuable-base-multipath.cpp b/test/unit-test/multi/test-continuable-base-multipath.cpp new file mode 100644 index 0000000..f10195f --- /dev/null +++ b/test/unit-test/multi/test-continuable-base-multipath.cpp @@ -0,0 +1,30 @@ + +/* + Copyright(c) 2015 - 2018 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#include + +TYPED_TEST(single_dimension_tests, are_recoverable) { + EXPECT_ASYNC_RESULT(this->supply().then([] () -> cti::expected<> { + return; // void + })); +} diff --git a/test/unit-test/single/test-continuable-expected.cpp b/test/unit-test/single/test-continuable-expected.cpp index 0bb2a79..c67a40a 100644 --- a/test/unit-test/single/test-continuable-expected.cpp +++ b/test/unit-test/single/test-continuable-expected.cpp @@ -23,12 +23,12 @@ #include #include +#include #include -#include #include -using cti::detail::container::expected; using cti::exception_t; +using cti::expected; static int const CANARY = 373671; @@ -125,27 +125,6 @@ TYPED_TEST(expected_all_tests, is_error_move_assignable) { EXPECT_TRUE(e.is_exception()); } -TYPED_TEST(expected_all_tests, is_value_assignable) { - { - TypeParam e; - e = this->supply(CANARY); - - EXPECT_TRUE(bool(e)); - EXPECT_EQ(this->get(*e), CANARY); - EXPECT_TRUE(e.is_value()); - EXPECT_FALSE(e.is_exception()); - } - - { - TypeParam e; - e = exception_t{}; - - EXPECT_FALSE(bool(e)); - EXPECT_FALSE(e.is_value()); - EXPECT_TRUE(e.is_exception()); - } -} - TEST(expected_copyable_tests, is_copy_constructible) { { copyable_type const e_old(CANARY); @@ -190,6 +169,64 @@ TEST(expected_copyable_tests, is_copy_assignable) { } } +TYPED_TEST(expected_all_tests, is_constructible_from_error_helper) { + cti::exceptional_expected e1(exception_t{}); + { + auto e2 = e1; + } + auto e2 = std::move(e1); + + TypeParam e(std::move(e2)); + + EXPECT_FALSE(bool(e)); + EXPECT_FALSE(e.is_value()); + EXPECT_TRUE(e.is_exception()); +} + +TYPED_TEST(expected_all_tests, is_assignable_from_error_helper) { + cti::exceptional_expected e1(exception_t{}); + { + auto e2 = e1; + } + auto e2 = std::move(e1); + + TypeParam e; + e = std::move(e2); + + EXPECT_FALSE(bool(e)); + EXPECT_FALSE(e.is_value()); + EXPECT_TRUE(e.is_exception()); +} + +TYPED_TEST(expected_all_tests, is_constructible_from_empty_helper) { + cti::empty_expected e1; + { + auto e2 = e1; + } + auto e2 = std::move(e1); + + TypeParam e(std::move(e2)); + + EXPECT_FALSE(bool(e)); + EXPECT_FALSE(e.is_value()); + EXPECT_TRUE(e.is_empty()); +} + +TYPED_TEST(expected_all_tests, is_assignable_from_empty_helper) { + cti::empty_expected e1; + { + auto e2 = e1; + } + auto e2 = std::move(e1); + + TypeParam e; + e = std::move(e2); + + EXPECT_FALSE(bool(e)); + EXPECT_FALSE(e.is_value()); + EXPECT_TRUE(e.is_empty()); +} + // This regression test shows a memory leak which happens when using the // expected class move constructed from another expected object. TEST(expected_single_test, test_leak_regression) {