From 6998d2ed729a310e98c38375e8d40103c30b1aa3 Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Tue, 19 Feb 2019 14:51:01 +0000 Subject: [PATCH] Correct swapping implementation --- CMakeLists.txt | 1 + tests/swap.cpp | 96 +++++++++ tl/expected.hpp | 535 ++++++++++++++++++++++++++++++------------------ 3 files changed, 432 insertions(+), 200 deletions(-) create mode 100644 tests/swap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0951b1e..bce2b55 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ if(EXPECTED_ENABLE_TESTS) ${CMAKE_CURRENT_SOURCE_DIR}/tests/issues.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/bases.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/observers.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/swap.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/constructors.cpp) add_executable(tests ${TEST_SOURCES}) diff --git a/tests/swap.cpp b/tests/swap.cpp new file mode 100644 index 0000000..a5e2b97 --- /dev/null +++ b/tests/swap.cpp @@ -0,0 +1,96 @@ +#include "catch.hpp" +#include "expected.hpp" + +struct no_throw { + no_throw(std::string i) : i(i) {} + std::string i; +}; +struct canthrow_move { + canthrow_move(std::string i) : i(i) {} + canthrow_move(canthrow_move const &) = default; + canthrow_move(canthrow_move &&other) noexcept(false) : i(other.i) {} + canthrow_move &operator=(canthrow_move &&) = default; + std::string i; +}; + +bool should_throw = false; +struct willthrow_move { + willthrow_move(std::string i) : i(i) {} + willthrow_move(willthrow_move const &) = default; + willthrow_move(willthrow_move &&other) : i(other.i) { + if (should_throw) + throw 0; + } + willthrow_move &operator=(willthrow_move &&) = default; + std::string i; +}; +static_assert(tl::detail::is_swappable::value, ""); + +template void swap_test() { + std::string s1 = "abcdefghijklmnopqrstuvwxyz"; + std::string s2 = "zyxwvutsrqponmlkjihgfedcba"; + + tl::expected a{s1}; + tl::expected b{s2}; + swap(a, b); + REQUIRE(a->i == s2); + REQUIRE(b->i == s1); + + a = s1; + b = tl::unexpected(s2); + swap(a, b); + REQUIRE(a.error().i == s2); + REQUIRE(b->i == s1); + + a = tl::unexpected(s1); + b = s2; + swap(a, b); + REQUIRE(a->i == s2); + REQUIRE(b.error().i == s1); + + a = tl::unexpected(s1); + b = tl::unexpected(s2); + swap(a, b); + REQUIRE(a.error().i == s2); + REQUIRE(b.error().i == s1); + + a = s1; + b = s2; + a.swap(b); + REQUIRE(a->i == s2); + REQUIRE(b->i == s1); + + a = s1; + b = tl::unexpected(s2); + a.swap(b); + REQUIRE(a.error().i == s2); + REQUIRE(b->i == s1); + + a = tl::unexpected(s1); + b = s2; + a.swap(b); + REQUIRE(a->i == s2); + REQUIRE(b.error().i == s1); + + a = tl::unexpected(s1); + b = tl::unexpected(s2); + a.swap(b); + REQUIRE(a.error().i == s2); + REQUIRE(b.error().i == s1); +} + +TEST_CASE("swap") { + + swap_test(); + swap_test(); + swap_test(); + + std::string s1 = "abcdefghijklmnopqrstuvwxyz"; + std::string s2 = "zyxwvutsrqponmlkjihgfedcbaxxx"; + tl::expected a{s1}; + tl::expected b{tl::unexpect, s2}; + should_throw = 1; + REQUIRE_THROWS(swap(a, b)); + REQUIRE(a->i == s1); + REQUIRE(b.error().i == s2); +} \ No newline at end of file diff --git a/tl/expected.hpp b/tl/expected.hpp index 740c6ad..1d475db 100644 --- a/tl/expected.hpp +++ b/tl/expected.hpp @@ -78,13 +78,13 @@ #ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX #define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX namespace tl { - namespace detail { - template - struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; +namespace detail { +template +struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; #ifdef _GLIBCXX_VECTOR - template - struct is_trivially_copy_constructible> - : std::is_trivially_copy_constructible{}; +template +struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible{}; #endif } } @@ -123,7 +123,7 @@ namespace tl { /// \exclude #define TL_EXPECTED_11_CONSTEXPR #else -/// \exclude + /// \exclude #define TL_EXPECTED_11_CONSTEXPR constexpr #endif @@ -222,13 +222,13 @@ namespace detail { template [[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - throw std::forward(e); + throw std::forward(e); #else - #ifdef _MSC_VER - __assume(0); - #else - __builtin_unreachable(); - #endif +#ifdef _MSC_VER + __assume(0); +#else + __builtin_unreachable(); +#endif #endif } @@ -285,6 +285,79 @@ using invoke_result = invoke_result_impl; template using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template tag swap(T &, T &); +template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + &&detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { +}; +#endif #endif // Trait for checking if a type is a tl::expected @@ -332,7 +405,7 @@ using is_copy_assignable_or_void = template using is_move_assignable_or_void = is_void_or>; - + } // namespace detail @@ -611,7 +684,7 @@ struct expected_operations_base : expected_storage_base { this->m_has_val = false; } - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED // These assign overloads ensure that the most efficient assignment // implementation is used while maintaining the strong exception guarantee. @@ -703,7 +776,7 @@ struct expected_operations_base : expected_storage_base { } } - #else +#else // If exceptions are disabled then we can just copy-construct void assign(const expected_operations_base &rhs) noexcept { @@ -724,7 +797,7 @@ struct expected_operations_base : expected_storage_base { } } - #endif +#endif // The common part of move/copy assigning template void assign_common(Rhs &&rhs) { @@ -732,7 +805,7 @@ struct expected_operations_base : expected_storage_base { if (rhs.m_has_val) { get() = std::forward(rhs).get(); } else { - destroy_val(); + destroy_val(); construct_error(std::forward(rhs).geterr()); } } else { @@ -820,7 +893,7 @@ struct expected_operations_base : expected_storage_base { #endif constexpr void destroy_val() { - //no-op + //no-op } }; @@ -1150,9 +1223,9 @@ class expected : private detail::expected_move_assign_base, static_assert(!std::is_reference::value, "E must not be a reference"); T *valptr() { return std::addressof(this->m_val); } - const T *valptr() const { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } unexpected *errptr() { return std::addressof(this->m_unexpect); } - const unexpected *errptr() const { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { return std::addressof(this->m_unexpect); } template ::value> * = nullptr> @@ -1309,7 +1382,7 @@ public: /// \synopsis template constexpr auto map(F &&f) &&; template TL_EXPECTED_11_CONSTEXPR decltype( - expected_map_impl(std::declval(), std::declval())) + expected_map_impl(std::declval(), std::declval())) map(F &&f) && { return expected_map_impl(std::move(*this), std::forward(f)); } @@ -1421,7 +1494,7 @@ public: /// \requires `F` is invokable with `E`, and `std::invoke_result_t` /// must be void or convertible to `expcted`. /// \effects If `*this` has a value, returns `*this`. - /// Otherwise, if `f` returns `void`, calls `std::forward(f)(E)` and returns + /// Otherwise, if `f` returns `void`, calls `std::forward(f)(E)` and returns /// `std::nullopt`. Otherwise, returns `std::forward(f)(E)`. /// /// \group or_else @@ -1531,7 +1604,7 @@ public: if (rhs.has_value()) { this->construct(*rhs); } else { - this->construct_error(rhs.error()); + this->construct_error(rhs.error()); } } @@ -1547,8 +1620,8 @@ public: if (rhs.has_value()) { this->construct(*rhs); } else { - this->construct_error(rhs.error()); - } + this->construct_error(rhs.error()); + } } template < @@ -1561,8 +1634,8 @@ public: if (rhs.has_value()) { this->construct(std::move(*rhs)); } else { - this->construct_error(std::move(rhs.error())); - } + this->construct_error(std::move(rhs.error())); + } } /// \exclude @@ -1576,8 +1649,8 @@ public: if (rhs.has_value()) { this->construct(std::move(*rhs)); } else { - this->construct_error(std::move(rhs.error())); - } + this->construct_error(std::move(rhs.error())); + } } template < @@ -1639,18 +1712,18 @@ public: auto tmp = std::move(err()); err().~unexpected(); - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { - ::new (valptr()) T(std::move(v)); + ::new (valptr()) T(std::forward(v)); this->m_has_val = true; } catch (...) { err() = std::move(tmp); throw; } - #else - ::new (valptr()) T(std::move(v)); - this->m_has_val = true; - #endif +#else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; +#endif } return *this; @@ -1708,7 +1781,7 @@ public: auto tmp = std::move(err()); err().~unexpected(); - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(std::forward(args)...); this->m_has_val = true; @@ -1716,10 +1789,10 @@ public: err() = std::move(tmp); throw; } - #else +#else ::new (valptr()) T(std::forward(args)...); this->m_has_val = true; - #endif +#endif } } @@ -1749,7 +1822,7 @@ public: auto tmp = std::move(err()); err().~unexpected(); - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED try { ::new (valptr()) T(il, std::forward(args)...); this->m_has_val = true; @@ -1757,140 +1830,201 @@ public: err() = std::move(tmp); throw; } - #else +#else ::new (valptr()) T(il, std::forward(args)...); this->m_has_val = true; - #endif +#endif } } - // TODO SFINAE - void swap(expected &rhs) noexcept( - std::is_nothrow_move_constructible::value &&noexcept( - swap(std::declval(), std::declval())) && - std::is_nothrow_move_constructible::value && - noexcept(swap(std::declval(), std::declval()))) { + template + detail::enable_if_t< + detail::is_swappable::value && detail::is_swappable::value && + std::is_nothrow_move_constructible::value && + !std::is_nothrow_move_constructible::value && !std::is_void::value> + swap(expected &rhs) noexcept( + detail::is_nothrow_swappable::value + &&std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { if (has_value() && rhs.has_value()) { using std::swap; swap(val(), rhs.val()); } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + auto temp = std::move(val()); + val().~T(); + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(temp); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + val() = std::move(temp); + throw; + } + } else { using std::swap; swap(err(), rhs.err()); - } else if (has_value()) { - auto temp = std::move(rhs.err()); - ::new (rhs.valptr()) T(val()); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } else { - auto temp = std::move(this->err()); - ::new (valptr()) T(rhs.val()); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); } } - /// \returns a pointer to the stored value - /// \requires a value is stored - /// \group pointer - constexpr const T *operator->() const { return valptr(); } - /// \group pointer - TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } - - /// \returns the stored value - /// \requires a value is stored - /// \group deref - template ::value> * = nullptr> - constexpr const U &operator*() const & { - return val(); - } - /// \group deref - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &operator*() & { - return val(); - } - /// \group deref - template ::value> * = nullptr> - constexpr const U &&operator*() const && { - return std::move(val()); - } - /// \group deref - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&operator*() && { - return std::move(val()); + template + detail::enable_if_t< + detail::is_swappable::value && detail::is_swappable::value && + std::is_nothrow_move_constructible::value && !std::is_void::value> + swap(expected &rhs) noexcept(std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value && + detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + using std::swap; + swap(val(), rhs.val()); + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); + try { + ::new (rhs.valptr()) T(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + rhs.err() = std::move(temp); + throw; + } + } else { + using std::swap; + swap(err(), rhs.err()); + } } - /// \returns whether or not the optional has a value - /// \group has_value - constexpr bool has_value() const noexcept { return this->m_has_val; } - /// \group has_value - constexpr explicit operator bool() const noexcept { return this->m_has_val; } + template + detail::enable_if_t::value && + detail::is_swappable::value && + std::is_void::value && + (std::is_move_constructible::value || + std::is_move_constructible::value)> + swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + // swapping void is a no-op + return; + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } else { + using std::swap; + swap(err(), rhs.err()); + } + } - /// \returns the contained value if there is one, otherwise throws - /// [bad_expected_access] - /// - /// \group value - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &value() const & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); - } - /// \group value - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &value() & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); - } - /// \group value - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &&value() const && { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return std::move(val()); - } - /// \group value - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&value() && { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return std::move(val()); - } + /// \returns a pointer to the stored value + /// \requires a value is stored + /// \group pointer + constexpr const T *operator->() const { return valptr(); } + /// \group pointer + TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } - /// \returns the unexpected value - /// \requires there is an unexpected value - /// \group error - constexpr const E &error() const & { return err().value(); } - /// \group error - TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } - /// \group error - constexpr const E &&error() const && { return std::move(err().value()); } - /// \group error - TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } + /// \returns the stored value + /// \requires a value is stored + /// \group deref + template ::value> * = nullptr> + constexpr const U &operator*() const & { + return val(); + } + /// \group deref + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + return val(); + } + /// \group deref + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + return std::move(val()); + } + /// \group deref + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + return std::move(val()); + } - /// \returns the stored value if there is one, otherwise returns `u` - /// \group value_or - template constexpr T value_or(U &&v) const & { - static_assert(std::is_copy_constructible::value && - std::is_convertible::value, - "T must be copy-constructible and convertible to from U&&"); - return bool(*this) ? **this : static_cast(std::forward(v)); - } - /// \group value_or - template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { - static_assert(std::is_move_constructible::value && - std::is_convertible::value, - "T must be move-constructible and convertible to from U&&"); - return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); - } + /// \returns whether or not the optional has a value + /// \group has_value + constexpr bool has_value() const noexcept { return this->m_has_val; } + /// \group has_value + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + /// \returns the contained value if there is one, otherwise throws + /// [bad_expected_access] + /// + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return std::move(val()); + } + /// \group value + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return std::move(val()); + } + + /// \returns the unexpected value + /// \requires there is an unexpected value + /// \group error + constexpr const E &error() const & { return err().value(); } + /// \group error + TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } + /// \group error + constexpr const E &&error() const && { return std::move(err().value()); } + /// \group error + TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } + + /// \returns the stored value if there is one, otherwise returns `u` + /// \group value_or + template constexpr T value_or(U &&v) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + /// \group value_or + template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } }; /// \exclude @@ -1948,7 +2082,7 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { #ifdef TL_EXPECTED_CXX14 template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> @@ -1960,7 +2094,7 @@ constexpr auto expected_map_impl(Exp &&exp, F &&f) { } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> @@ -1985,7 +2119,7 @@ constexpr auto expected_map_impl(Exp &&exp, F &&f) { } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> auto expected_map_impl(Exp &&exp, F &&f) { @@ -1996,10 +2130,10 @@ auto expected_map_impl(Exp &&exp, F &&f) { } return result(unexpect, std::forward(exp).error()); -} +} #else template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> @@ -2014,7 +2148,7 @@ constexpr auto expected_map_impl(Exp &&exp, F &&f) } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> @@ -2029,7 +2163,7 @@ auto expected_map_impl(Exp &&exp, F &&f) -> expected> { } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> @@ -2042,7 +2176,7 @@ constexpr auto expected_map_impl(Exp &&exp, F &&f) } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval())), detail::enable_if_t::value> * = nullptr> @@ -2053,13 +2187,13 @@ auto expected_map_impl(Exp &&exp, F &&f) -> expected> { } return unexpected>(std::forward(exp).error()); -} +} #endif #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2071,7 +2205,7 @@ constexpr auto map_error_impl(Exp &&exp, F &&f) { std::forward(exp).error())); } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2085,7 +2219,7 @@ auto map_error_impl(Exp &&exp, F &&f) { return result(unexpect, monostate{}); } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2097,7 +2231,7 @@ constexpr auto map_error_impl(Exp &&exp, F &&f) { std::forward(exp).error())); } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2109,10 +2243,10 @@ auto map_error_impl(Exp &&exp, F &&f) { detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); -} +} #else template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2127,7 +2261,7 @@ constexpr auto map_error_impl(Exp &&exp, F &&f) } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2142,7 +2276,7 @@ auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2157,7 +2291,7 @@ constexpr auto map_error_impl(Exp &&exp, F &&f) } template >::value> * = nullptr, + detail::enable_if_t>::value> * = nullptr, class Ret = decltype(detail::invoke(std::declval(), std::declval().error())), detail::enable_if_t::value> * = nullptr> @@ -2169,7 +2303,7 @@ auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); -} +} #endif #ifdef TL_EXPECTED_CXX14 @@ -2178,6 +2312,28 @@ template ().error())), detail::enable_if_t::value> * = nullptr> constexpr auto or_else_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() + ? std::forward(exp) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() + ? std::forward(exp) + : (detail::invoke(std::forward(f), std::forward(exp).error()), + std::forward(exp)); +} +#else +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { static_assert(detail::is_expected::value, "F must return an expected"); return exp.has_value() ? std::forward(exp) @@ -2192,29 +2348,7 @@ detail::decay_t or_else_impl(Exp &&exp, F &&f) { return exp.has_value() ? std::forward(exp) : (detail::invoke(std::forward(f), std::forward(exp).error()), - std::forward(exp)); -} -#else -template (), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto or_else_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - return exp.has_value() - ? std::forward(exp) - : detail::invoke(std::forward(f), std::forward(exp).error()); -} - -template (), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -detail::decay_t or_else_impl(Exp &&exp, F &&f) { - return exp.has_value() - ? std::forward(exp) - : (detail::invoke(std::forward(f), std::forward(exp).error()), - std::forward(exp)); + std::forward(exp)); } #endif } // namespace detail @@ -2268,10 +2402,11 @@ constexpr bool operator!=(const unexpected &e, const expected &x) { return x.has_value() ? true : x.error() != e.value(); } -// TODO is_swappable template ::value && - std::is_move_constructible::value> * = nullptr> + detail::enable_if_t<(std::is_void::value || std::is_move_constructible::value) && + detail::is_swappable::value && + std::is_move_constructible::value && + detail::is_swappable::value> * = nullptr> void swap(expected &lhs, expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { lhs.swap(rhs);