From d420c9752cdbdaf2b68e16a453ae3dd4a5413b8e Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Sun, 2 Jan 2022 14:48:19 -0500 Subject: [PATCH 1/5] Prepare and_then/map/transform/map_error/or_else for SFINAE-friendliness. This incidentally simplifies the preprocessor stuff, by making these codepaths look the same in both C++11 and C++14. The old code had the weird effect that the C++11 codepath was slightly *more* SFINAE-friendly than the C++14 codepath. This patch doesn't actually make and_then/map/transform/map_error/or_else be SFINAE-friendly. But it lays the groundwork for adding further constraints to the `enable_if` stuff in `and_then_impl` etc., which could make these functions fully SFINAE-friendly without needing to touch the non-`_impl` versions again. --- include/tl/expected.hpp | 171 +++++++++++----------------------------- 1 file changed, 47 insertions(+), 124 deletions(-) diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index 31b130a..f419aa6 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -1251,198 +1251,121 @@ class expected : private detail::expected_move_assign_base, typedef E error_type; typedef unexpected unexpected_type; -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { - return and_then_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { - return and_then_impl(std::move(*this), std::forward(f)); - } - template constexpr auto and_then(F &&f) const & { - return and_then_impl(*this, std::forward(f)); - } - -#ifndef TL_EXPECTED_NO_CONSTRR - template constexpr auto and_then(F &&f) const && { - return and_then_impl(std::move(*this), std::forward(f)); - } -#endif - -#else template - TL_EXPECTED_11_CONSTEXPR auto - and_then(F &&f) & -> decltype(and_then_impl(std::declval(), std::forward(f))) { + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & + -> decltype(and_then_impl(*this, std::forward(f))) { return and_then_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && + -> decltype(and_then_impl(std::move(*this), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } template - constexpr auto and_then(F &&f) const & -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + constexpr auto and_then(F &&f) const & + -> decltype(and_then_impl(*this, std::forward(f))) { return and_then_impl(*this, std::forward(f)); } - #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr auto and_then(F &&f) const && -> decltype( - and_then_impl(std::declval(), std::forward(f))) { + constexpr auto and_then(F &&f) const && + -> decltype(and_then_impl(std::move(*this), std::forward(f))) { return and_then_impl(std::move(*this), std::forward(f)); } #endif -#endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { - return expected_map_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template constexpr auto map(F &&f) const & { - return expected_map_impl(*this, std::forward(f)); - } - template constexpr auto map(F &&f) const && { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#else template - TL_EXPECTED_11_CONSTEXPR decltype( - expected_map_impl(std::declval(), std::declval())) - map(F &&f) & { + TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & + -> decltype(expected_map_impl(*this, std::forward(f))) { return expected_map_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR decltype( - expected_map_impl(std::declval(), std::declval())) - map(F &&f) && { + TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && + -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { return expected_map_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F &&f) const & { + constexpr auto map(F &&f) const & + -> decltype(expected_map_impl(*this, std::forward(f))) { return expected_map_impl(*this, std::forward(f)); } - #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - map(F &&f) const && { + constexpr auto map(F &&f) const && + -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { return expected_map_impl(std::move(*this), std::forward(f)); } #endif -#endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { - return expected_map_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template constexpr auto transform(F &&f) const & { - return expected_map_impl(*this, std::forward(f)); - } - template constexpr auto transform(F &&f) const && { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#else - template - TL_EXPECTED_11_CONSTEXPR decltype( - expected_map_impl(std::declval(), std::declval())) - transform(F &&f) & { + template + TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & + -> decltype(expected_map_impl(*this, std::forward(f))) { return expected_map_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR decltype( - expected_map_impl(std::declval(), std::declval())) - transform(F &&f) && { + TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && + -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { return expected_map_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F &&f) const & { + constexpr auto transform(F &&f) const & + -> decltype(expected_map_impl(*this, std::forward(f))) { return expected_map_impl(*this, std::forward(f)); } - #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(expected_map_impl(std::declval(), - std::declval())) - transform(F &&f) const && { + constexpr auto transform(F &&f) const && + -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { return expected_map_impl(std::move(*this), std::forward(f)); } #endif -#endif -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) - template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { - return map_error_impl(*this, std::forward(f)); - } - template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { - return map_error_impl(std::move(*this), std::forward(f)); - } - template constexpr auto map_error(F &&f) const & { - return map_error_impl(*this, std::forward(f)); - } - template constexpr auto map_error(F &&f) const && { - return map_error_impl(std::move(*this), std::forward(f)); - } -#else template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) & { + TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & + -> decltype(map_error_impl(*this, std::forward(f))) { return map_error_impl(*this, std::forward(f)); } template - TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) && { + TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && + -> decltype(map_error_impl(std::move(*this), std::forward(f))) { return map_error_impl(std::move(*this), std::forward(f)); } template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) const & { + constexpr auto map_error(F &&f) const & + -> decltype(map_error_impl(*this, std::forward(f))) { return map_error_impl(*this, std::forward(f)); } - #ifndef TL_EXPECTED_NO_CONSTRR template - constexpr decltype(map_error_impl(std::declval(), - std::declval())) - map_error(F &&f) const && { + constexpr auto map_error(F &&f) const && + -> decltype(map_error_impl(std::move(*this), std::forward(f))) { return map_error_impl(std::move(*this), std::forward(f)); } #endif -#endif - template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + + template + TL_EXPECTED_11_CONSTEXPR auto or_else(F &&f) & + -> decltype(or_else_impl(*this, std::forward(f))) { return or_else_impl(*this, std::forward(f)); } - - template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + template + TL_EXPECTED_11_CONSTEXPR auto or_else(F &&f) && + -> decltype(or_else_impl(std::move(*this), std::forward(f))) { return or_else_impl(std::move(*this), std::forward(f)); } - - template expected constexpr or_else(F &&f) const & { + template + constexpr auto or_else(F &&f) const & + -> decltype(or_else_impl(*this, std::forward(f))) { return or_else_impl(*this, std::forward(f)); } - #ifndef TL_EXPECTED_NO_CONSTRR - template expected constexpr or_else(F &&f) const && { + template + constexpr auto or_else(F &&f) const && + -> decltype(or_else_impl(std::move(*this), std::forward(f))) { return or_else_impl(std::move(*this), std::forward(f)); } #endif + constexpr expected() = default; constexpr expected(const expected &rhs) = default; constexpr expected(expected &&rhs) = default; From 05f4c98bd3371f8617aa1f70187bac004885827f Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Sun, 2 Jan 2022 15:53:35 -0500 Subject: [PATCH 2/5] Move and_then_impl above its first use; ADL-proof its use. This mainly just swaps two big blocks of code. But it also adds `detail::` in front of every call to `foo_impl`; and it renames `expected_map_impl` to simply `map_impl` because that seems more consistent with the rest of the names. --- include/tl/expected.hpp | 1730 +++++++++++++++++++-------------------- 1 file changed, 865 insertions(+), 865 deletions(-) diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index f419aa6..88378e6 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -1203,987 +1203,987 @@ template class bad_expected_access : public std::exception { E m_val; }; -/// An `expected` object is an object that contains the storage for -/// another object and manages the lifetime of this contained object `T`. -/// Alternatively it could contain the storage for another unexpected object -/// `E`. The contained object may not be initialized after the expected object -/// has been initialized, and may not be destroyed before the expected object -/// has been destroyed. The initialization state of the contained object is -/// tracked by the expected object. -template -class expected : private detail::expected_move_assign_base, - private detail::expected_delete_ctor_base, - private detail::expected_delete_assign_base, - private detail::expected_default_ctor_base { - static_assert(!std::is_reference::value, "T must not be a reference"); - static_assert(!std::is_same::type>::value, - "T must not be in_place_t"); - static_assert(!std::is_same::type>::value, - "T must not be unexpect_t"); - static_assert(!std::is_same>::type>::value, - "T must not be unexpected"); - static_assert(!std::is_reference::value, "E must not be a reference"); +namespace detail { +template using exp_t = typename detail::decay_t::value_type; +template using err_t = typename detail::decay_t::error_type; +template using ret_t = expected>; - T *valptr() { 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); } +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &val() { - return this->m_val; - } - TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} - template ::value> * = nullptr> - constexpr const U &val() const { - return this->m_val; - } - constexpr const unexpected &err() const { return this->m_unexpect; } +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); - using impl_base = detail::expected_move_assign_base; - using ctor_base = detail::expected_default_ctor_base; + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#else +template struct TC; +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); -public: - typedef T value_type; - typedef E error_type; - typedef unexpected unexpected_type; + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} - template - TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & - -> decltype(and_then_impl(*this, std::forward(f))) { - return and_then_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && - -> decltype(and_then_impl(std::move(*this), std::forward(f))) { - return and_then_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto and_then(F &&f) const & - -> decltype(and_then_impl(*this, std::forward(f))) { - return and_then_impl(*this, std::forward(f)); - } -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto and_then(F &&f) const && - -> decltype(and_then_impl(std::move(*this), std::forward(f))) { - return and_then_impl(std::move(*this), std::forward(f)); - } -#endif +template ())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); - template - TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & - -> decltype(expected_map_impl(*this, std::forward(f))) { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && - -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto map(F &&f) const & - -> decltype(expected_map_impl(*this, std::forward(f))) { - return expected_map_impl(*this, std::forward(f)); - } -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto map(F &&f) const && - -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { - return expected_map_impl(std::move(*this), std::forward(f)); - } + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} #endif - template - TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & - -> decltype(expected_map_impl(*this, std::forward(f))) { - return expected_map_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && - -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { - return expected_map_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto transform(F &&f) const & - -> decltype(expected_map_impl(*this, std::forward(f))) { - return expected_map_impl(*this, std::forward(f)); - } -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto transform(F &&f) const && - -> decltype(expected_map_impl(std::move(*this), std::forward(f))) { - return expected_map_impl(std::move(*this), std::forward(f)); - } -#endif +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} - template - TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & - -> decltype(map_error_impl(*this, std::forward(f))) { - return map_error_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && - -> decltype(map_error_impl(std::move(*this), std::forward(f))) { - return map_error_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto map_error(F &&f) const & - -> decltype(map_error_impl(*this, std::forward(f))) { - return map_error_impl(*this, std::forward(f)); - } -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto map_error(F &&f) const && - -> decltype(map_error_impl(std::move(*this), std::forward(f))) { - return map_error_impl(std::move(*this), std::forward(f)); +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); } -#endif - template - TL_EXPECTED_11_CONSTEXPR auto or_else(F &&f) & - -> decltype(or_else_impl(*this, std::forward(f))) { - return or_else_impl(*this, std::forward(f)); - } - template - TL_EXPECTED_11_CONSTEXPR auto or_else(F &&f) && - -> decltype(or_else_impl(std::move(*this), std::forward(f))) { - return or_else_impl(std::move(*this), std::forward(f)); - } - template - constexpr auto or_else(F &&f) const & - -> decltype(or_else_impl(*this, std::forward(f))) { - return or_else_impl(*this, std::forward(f)); - } -#ifndef TL_EXPECTED_NO_CONSTRR - template - constexpr auto or_else(F &&f) const && - -> decltype(or_else_impl(std::move(*this), std::forward(f))) { - return or_else_impl(std::move(*this), std::forward(f)); - } -#endif + return result(unexpect, std::forward(exp).error()); +} - constexpr expected() = default; - constexpr expected(const expected &rhs) = default; - constexpr expected(expected &&rhs) = default; - expected &operator=(const expected &rhs) = default; - expected &operator=(expected &&rhs) = default; +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} - template ::value> * = - nullptr> - constexpr expected(in_place_t, Args &&... args) - : impl_base(in_place, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +auto map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return result(); + } - template &, Args &&...>::value> * = nullptr> - constexpr expected(in_place_t, std::initializer_list il, Args &&... args) - : impl_base(in_place, il, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} + return result(unexpect, std::forward(exp).error()); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> - template ::value> * = - nullptr, - detail::enable_if_t::value> * = - nullptr> - explicit constexpr expected(const unexpected &e) - : impl_base(unexpect, e.value()), - ctor_base(detail::default_constructor_tag{}) {} +constexpr auto map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; - template < - class G = E, - detail::enable_if_t::value> * = - nullptr, - detail::enable_if_t::value> * = nullptr> - constexpr expected(unexpected const &e) - : impl_base(unexpect, e.value()), - ctor_base(detail::default_constructor_tag{}) {} + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} - template < - class G = E, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t::value> * = nullptr> - explicit constexpr expected(unexpected &&e) noexcept( - std::is_nothrow_constructible::value) - : impl_base(unexpect, std::move(e.value())), - ctor_base(detail::default_constructor_tag{}) {} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> - template < - class G = E, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t::value> * = nullptr> - constexpr expected(unexpected &&e) noexcept( - std::is_nothrow_constructible::value) - : impl_base(unexpect, std::move(e.value())), - ctor_base(detail::default_constructor_tag{}) {} +auto map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } - template ::value> * = - nullptr> - constexpr explicit expected(unexpect_t, Args &&... args) - : impl_base(unexpect, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} + return unexpected>(std::forward(exp).error()); +} - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected(unexpect_t, std::initializer_list il, - Args &&... args) - : impl_base(unexpect, il, std::forward(args)...), - ctor_base(detail::default_constructor_tag{}) {} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> - template ::value && - std::is_convertible::value)> * = - nullptr, - detail::expected_enable_from_other - * = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } +constexpr auto map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; - template ::value && - std::is_convertible::value)> * = - nullptr, - detail::expected_enable_from_other - * = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(*rhs); - } else { - this->construct_error(rhs.error()); - } - } + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} - template < - class U, class G, - detail::enable_if_t::value && - std::is_convertible::value)> * = nullptr, - detail::expected_enable_from_other * = nullptr> - explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } - } +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> - template < - class U, class G, - detail::enable_if_t<(std::is_convertible::value && - std::is_convertible::value)> * = nullptr, - detail::expected_enable_from_other * = nullptr> - TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) - : ctor_base(detail::default_constructor_tag{}) { - if (rhs.has_value()) { - this->construct(std::move(*rhs)); - } else { - this->construct_error(std::move(rhs.error())); - } +auto map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return {}; } - template < - class U = T, - detail::enable_if_t::value> * = nullptr, - detail::expected_enable_forward_value * = nullptr> - explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) - : expected(in_place, std::forward(v)) {} - - template < - class U = T, - detail::enable_if_t::value> * = nullptr, - detail::expected_enable_forward_value * = nullptr> - TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) - : expected(in_place, std::forward(v)) {} + return unexpected>(std::forward(exp).error()); +} +#endif - template < - class U = T, class G = T, - detail::enable_if_t::value> * = - nullptr, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t< - (!std::is_same, detail::decay_t>::value && - !detail::conjunction, - std::is_same>>::value && - std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)> * = nullptr> - expected &operator=(U &&v) { - if (has_value()) { - val() = std::forward(v); - } else { - err().~unexpected(); - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - } +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } - return *this; + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); } - template < - class U = T, class G = T, - detail::enable_if_t::value> * = - nullptr, - detail::enable_if_t::value> * = nullptr, - detail::enable_if_t< - (!std::is_same, detail::decay_t>::value && - !detail::conjunction, - std::is_same>>::value && - std::is_constructible::value && - std::is_assignable::value && - std::is_nothrow_move_constructible::value)> * = nullptr> - expected &operator=(U &&v) { - if (has_value()) { - val() = std::forward(v); - } else { - auto tmp = std::move(err()); - err().~unexpected(); + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } - #else - ::new (valptr()) T(std::forward(v)); - this->m_has_val = true; - #endif - } + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} - return *this; +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); } - template ::value && - std::is_assignable::value> * = nullptr> - expected &operator=(const unexpected &rhs) { - if (!has_value()) { - err() = rhs; - } else { - this->destroy_val(); - ::new (errptr()) unexpected(rhs); - this->m_has_val = false; - } - - return *this; - } + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} - template ::value && - std::is_move_assignable::value> * = nullptr> - expected &operator=(unexpected &&rhs) noexcept { - if (!has_value()) { - err() = std::move(rhs); - } else { - this->destroy_val(); - ::new (errptr()) unexpected(std::move(rhs)); - this->m_has_val = false; - } +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; - return *this; - } + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} - template ::value> * = nullptr> - void emplace(Args &&... args) { - if (has_value()) { - val() = T(std::forward(args)...); - } else { - err().~unexpected(); - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; - } +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); } - template ::value> * = nullptr> - void emplace(Args &&... args) { - if (has_value()) { - val() = T(std::forward(args)...); - } else { - auto tmp = std::move(err()); - err().~unexpected(); + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } - #else - ::new (valptr()) T(std::forward(args)...); - this->m_has_val = true; - #endif - } - } +#ifdef TL_EXPECTED_CXX14 +template (), + std::declval().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 &, Args &&...>::value> * = nullptr> - void emplace(std::initializer_list il, Args &&... args) { - if (has_value()) { - T t(il, std::forward(args)...); - val() = std::move(t); - } else { - err().~unexpected(); - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - } - } +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) + : detail::invoke(std::forward(f), std::forward(exp).error()); +} - template &, Args &&...>::value> * = nullptr> - void emplace(std::initializer_list il, Args &&... args) { - if (has_value()) { - T t(il, std::forward(args)...); - val() = std::move(t); - } else { - auto tmp = std::move(err()); - err().~unexpected(); +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)); +} +#endif +} // namespace detail - #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } - #else - ::new (valptr()) T(il, std::forward(args)...); - this->m_has_val = true; - #endif - } - } +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same::type>::value, + "T must not be in_place_t"); + static_assert(!std::is_same::type>::value, + "T must not be unexpect_t"); + static_assert(!std::is_same>::type>::value, + "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); -private: - using t_is_void = std::true_type; - using t_is_not_void = std::false_type; - using t_is_nothrow_move_constructible = std::true_type; - using move_constructing_t_can_throw = std::false_type; - using e_is_nothrow_move_constructible = std::true_type; - using move_constructing_e_can_throw = std::false_type; + T *valptr() { 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); } - void swap_where_both_have_value(expected &/*rhs*/ , t_is_void) noexcept { - // swapping void is a no-op + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &val() { + return this->m_val; } + TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } - void swap_where_both_have_value(expected &rhs, t_is_not_void) { - using std::swap; - swap(val(), rhs.val()); + template ::value> * = nullptr> + constexpr const U &val() const { + return this->m_val; } + constexpr const unexpected &err() const { return this->m_unexpect; } - void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( - std::is_nothrow_move_constructible::value) { - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - std::swap(this->m_has_val, rhs.m_has_val); - } + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; - void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { - swap_where_only_one_has_value_and_t_is_not_void( - rhs, typename std::is_nothrow_move_constructible::type{}, - typename std::is_nothrow_move_constructible::type{}); - } +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; - void swap_where_only_one_has_value_and_t_is_not_void( - expected &rhs, t_is_nothrow_move_constructible, - e_is_nothrow_move_constructible) noexcept { - auto temp = std::move(val()); - val().~T(); - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); + template + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & + -> decltype(detail::and_then_impl(*this, std::forward(f))) { + return detail::and_then_impl(*this, std::forward(f)); } - - void swap_where_only_one_has_value_and_t_is_not_void( - expected &rhs, t_is_nothrow_move_constructible, - move_constructing_e_can_throw) { - auto temp = std::move(val()); - val().~T(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - try { - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); - } catch (...) { - val() = std::move(temp); - throw; - } -#else - ::new (errptr()) unexpected_type(std::move(rhs.err())); - rhs.err().~unexpected_type(); - ::new (rhs.valptr()) T(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); -#endif + template + TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && + -> decltype(detail::and_then_impl(std::move(*this), std::forward(f))) { + return detail::and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_then(F &&f) const & + -> decltype(detail::and_then_impl(*this, std::forward(f))) { + return detail::and_then_impl(*this, std::forward(f)); + } +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && + -> decltype(detail::and_then_impl(std::move(*this), std::forward(f))) { + return detail::and_then_impl(std::move(*this), std::forward(f)); } +#endif - void swap_where_only_one_has_value_and_t_is_not_void( - expected &rhs, move_constructing_t_can_throw, - t_is_nothrow_move_constructible) { - auto temp = std::move(rhs.err()); - rhs.err().~unexpected_type(); -#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED - 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 - ::new (rhs.valptr()) T(val()); - val().~T(); - ::new (errptr()) unexpected_type(std::move(temp)); - std::swap(this->m_has_val, rhs.m_has_val); -#endif + template + TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & + -> decltype(detail::map_impl(*this, std::forward(f))) { + return detail::map_impl(*this, std::forward(f)); } - -public: - template - detail::enable_if_t::value && - detail::is_swappable::value && - (std::is_nothrow_move_constructible::value || - std::is_nothrow_move_constructible::value)> - swap(expected &rhs) noexcept( - std::is_nothrow_move_constructible::value - &&detail::is_nothrow_swappable::value - &&std::is_nothrow_move_constructible::value - &&detail::is_nothrow_swappable::value) { - if (has_value() && rhs.has_value()) { - swap_where_both_have_value(rhs, typename std::is_void::type{}); - } else if (!has_value() && rhs.has_value()) { - rhs.swap(*this); - } else if (has_value()) { - swap_where_only_one_has_value(rhs, typename std::is_void::type{}); - } else { - using std::swap; - swap(err(), rhs.err()); - } + template + TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && + -> decltype(detail::map_impl(std::move(*this), std::forward(f))) { + return detail::map_impl(std::move(*this), std::forward(f)); } + template + constexpr auto map(F &&f) const & + -> decltype(detail::map_impl(*this, std::forward(f))) { + return detail::map_impl(*this, std::forward(f)); + } +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto map(F &&f) const && + -> decltype(detail::map_impl(std::move(*this), std::forward(f))) { + return detail::map_impl(std::move(*this), std::forward(f)); + } +#endif - constexpr const T *operator->() const { return valptr(); } - TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } - - template ::value> * = nullptr> - constexpr const U &operator*() const & { - return val(); + template + TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & + -> decltype(detail::map_impl(*this, std::forward(f))) { + return detail::map_impl(*this, std::forward(f)); } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &operator*() & { - return val(); + template + TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && + -> decltype(detail::map_impl(std::move(*this), std::forward(f))) { + return detail::map_impl(std::move(*this), std::forward(f)); } - template ::value> * = nullptr> - constexpr const U &&operator*() const && { - return std::move(val()); + template + constexpr auto transform(F &&f) const & + -> decltype(detail::map_impl(*this, std::forward(f))) { + return detail::map_impl(*this, std::forward(f)); } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&operator*() && { - return std::move(val()); +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto transform(F &&f) const && + -> decltype(detail::map_impl(std::move(*this), std::forward(f))) { + return detail::map_impl(std::move(*this), std::forward(f)); } +#endif - constexpr bool has_value() const noexcept { return this->m_has_val; } - constexpr explicit operator bool() const noexcept { return this->m_has_val; } - - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &value() const & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); + template + TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & + -> decltype(detail::map_error_impl(*this, std::forward(f))) { + return detail::map_error_impl(*this, std::forward(f)); } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &value() & { - if (!has_value()) - detail::throw_exception(bad_expected_access(err().value())); - return val(); + template + TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && + -> decltype(detail::map_error_impl(std::move(*this), std::forward(f))) { + return detail::map_error_impl(std::move(*this), std::forward(f)); } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR const U &&value() const && { - if (!has_value()) - detail::throw_exception(bad_expected_access(std::move(err()).value())); - return std::move(val()); + template + constexpr auto map_error(F &&f) const & + -> decltype(detail::map_error_impl(*this, std::forward(f))) { + return detail::map_error_impl(*this, std::forward(f)); } - template ::value> * = nullptr> - TL_EXPECTED_11_CONSTEXPR U &&value() && { - if (!has_value()) - detail::throw_exception(bad_expected_access(std::move(err()).value())); - return std::move(val()); +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto map_error(F &&f) const && + -> decltype(detail::map_error_impl(std::move(*this), std::forward(f))) { + return detail::map_error_impl(std::move(*this), std::forward(f)); } +#endif - constexpr const E &error() const & { return err().value(); } - TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } - constexpr const E &&error() const && { return std::move(err().value()); } - TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } - - 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)); + template + TL_EXPECTED_11_CONSTEXPR auto or_else(F &&f) & + -> decltype(detail::or_else_impl(*this, std::forward(f))) { + return detail::or_else_impl(*this, std::forward(f)); } - 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)); + template + TL_EXPECTED_11_CONSTEXPR auto or_else(F &&f) && + -> decltype(detail::or_else_impl(std::move(*this), std::forward(f))) { + return detail::or_else_impl(std::move(*this), std::forward(f)); } -}; + template + constexpr auto or_else(F &&f) const & + -> decltype(detail::or_else_impl(*this, std::forward(f))) { + return detail::or_else_impl(*this, std::forward(f)); + } +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto or_else(F &&f) const && + -> decltype(detail::or_else_impl(std::move(*this), std::forward(f))) { + return detail::or_else_impl(std::move(*this), std::forward(f)); + } +#endif -namespace detail { -template using exp_t = typename detail::decay_t::value_type; -template using err_t = typename detail::decay_t::error_type; -template using ret_t = expected>; + constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; -#ifdef TL_EXPECTED_CXX14 -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval()))> -constexpr auto and_then_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); + template ::value> * = + nullptr> + constexpr expected(in_place_t, Args &&... args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} + template &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&... args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval()))> -constexpr auto and_then_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#else -template struct TC; -template (), - *std::declval())), - detail::enable_if_t>::value> * = nullptr> -auto and_then_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); + template < + class G = E, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} -template ())), - detail::enable_if_t>::value> * = nullptr> -constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#endif + template ::value> * = + nullptr> + constexpr explicit expected(unexpect_t, Args &&... args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} -#ifdef TL_EXPECTED_CXX14 -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp &&exp, F &&f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, + Args &&... args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> -auto expected_map_impl(Exp &&exp, F &&f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return result(); + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } } - return result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> -constexpr auto expected_map_impl(Exp &&exp, F &&f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> -auto expected_map_impl(Exp &&exp, F &&f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return result(); + template < + class U, class G, + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } } - return result(unexpect, std::forward(exp).error()); -} -#else -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> + template < + class U, class G, + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } -constexpr auto expected_map_impl(Exp &&exp, F &&f) - -> ret_t> { - using result = ret_t>; + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } -auto expected_map_impl(Exp &&exp, F &&f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return {}; + return *this; } - return unexpected>(std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + auto tmp = std::move(err()); + err().~unexpected(); -constexpr auto expected_map_impl(Exp &&exp, F &&f) - -> ret_t> { - using result = ret_t>; + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + #endif + } - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} + return *this; + } -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> + template ::value && + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } else { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } -auto expected_map_impl(Exp &&exp, F &&f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return {}; + return *this; } - return unexpected>(std::forward(exp).error()); -} -#endif + template ::value && + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } else { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } -#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ - !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, detail::decay_t>; - return exp.has_value() - ? result(*std::forward(exp)) - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, monostate>; - if (exp.has_value()) { - return result(*std::forward(exp)); + return *this; } - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, detail::decay_t>; - return exp.has_value() - ? result() - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) { - using result = expected, monostate>; - if (exp.has_value()) { - return result(); + template ::value> * = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } } - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -#else -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) - -> expected, detail::decay_t> { - using result = expected, detail::decay_t>; + template ::value> * = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } else { + auto tmp = std::move(err()); + err().~unexpected(); - return exp.has_value() - ? result(*std::forward(exp)) - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + #endif + } + } -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { - using result = expected, monostate>; - if (exp.has_value()) { - return result(*std::forward(exp)); + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } } - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + auto tmp = std::move(err()); + err().~unexpected(); -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) - -> expected, detail::decay_t> { - using result = expected, detail::decay_t>; + #ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + #else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + #endif + } + } - return exp.has_value() - ? result() - : result(unexpect, detail::invoke(std::forward(f), - std::forward(exp).error())); -} +private: + using t_is_void = std::true_type; + using t_is_not_void = std::false_type; + using t_is_nothrow_move_constructible = std::true_type; + using move_constructing_t_can_throw = std::false_type; + using e_is_nothrow_move_constructible = std::true_type; + using move_constructing_e_can_throw = std::false_type; -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - std::declval().error())), - detail::enable_if_t::value> * = nullptr> -auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { - using result = expected, monostate>; - if (exp.has_value()) { - return result(); + void swap_where_both_have_value(expected &/*rhs*/ , t_is_void) noexcept { + // swapping void is a no-op } - detail::invoke(std::forward(f), std::forward(exp).error()); - return result(unexpect, monostate{}); -} -#endif + void swap_where_both_have_value(expected &rhs, t_is_not_void) { + using std::swap; + swap(val(), rhs.val()); + } -#ifdef TL_EXPECTED_CXX14 -template (), - std::declval().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()); -} + void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( + std::is_nothrow_move_constructible::value) { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } -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)); -} + void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { + swap_where_only_one_has_value_and_t_is_not_void( + rhs, typename std::is_nothrow_move_constructible::type{}, + typename std::is_nothrow_move_constructible::type{}); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + e_is_nothrow_move_constructible) noexcept { + auto temp = std::move(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + move_constructing_e_can_throw) { + auto temp = std::move(val()); + val().~T(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + val() = std::move(temp); + throw; + } #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()); -} + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } -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)); -} + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, move_constructing_t_can_throw, + t_is_nothrow_move_constructible) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + 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 + ::new (rhs.valptr()) T(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); #endif -} // namespace detail + } + +public: + template + detail::enable_if_t::value && + detail::is_swappable::value && + (std::is_nothrow_move_constructible::value || + std::is_nothrow_move_constructible::value)> + swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value + &&std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + swap_where_both_have_value(rhs, typename std::is_void::type{}); + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + swap_where_only_one_has_value(rhs, typename std::is_void::type{}); + } else { + using std::swap; + swap(err(), rhs.err()); + } + } + + constexpr const T *operator->() const { return valptr(); } + TL_EXPECTED_11_CONSTEXPR T *operator->() { return valptr(); } + + template ::value> * = nullptr> + constexpr const U &operator*() const & { + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + return val(); + } + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + return std::move(val()); + } + + constexpr bool has_value() const noexcept { return this->m_has_val; } + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + + constexpr const E &error() const & { return err().value(); } + TL_EXPECTED_11_CONSTEXPR E &error() & { return err().value(); } + constexpr const E &&error() const && { return std::move(err().value()); } + TL_EXPECTED_11_CONSTEXPR E &&error() && { return std::move(err().value()); } + + 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)); + } + 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)); + } +}; template constexpr bool operator==(const expected &lhs, From 0258ed605bcc86c05c821ab6b86ab717a62433ff Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Sun, 2 Jan 2022 15:48:24 -0500 Subject: [PATCH 3/5] SFINAE-friendly `and_then` --- include/tl/expected.hpp | 45 +++++++------------------- tests/extensions.cpp | 71 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 34 deletions(-) diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index 88378e6..70939d0 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -1208,14 +1208,14 @@ template using exp_t = typename detail::decay_t::value_type; template using err_t = typename detail::decay_t::error_type; template using ret_t = expected>; -#ifdef TL_EXPECTED_CXX14 template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval()))> -constexpr auto and_then_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); - + class Ret = detail::decay_t(), + *std::declval()))>, + detail::enable_if_t::value && + std::is_convertible().error()), + err_t>::value> * = nullptr> +constexpr Ret and_then_impl(Exp &&exp, F &&f) { return exp.has_value() ? detail::invoke(std::forward(f), *std::forward(exp)) : Ret(unexpect, std::forward(exp).error()); @@ -1223,37 +1223,14 @@ constexpr auto and_then_impl(Exp &&exp, F &&f) { template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval()))> -constexpr auto and_then_impl(Exp &&exp, F &&f) { - static_assert(detail::is_expected::value, "F must return an expected"); - + class Ret = detail::decay_t()))>, + detail::enable_if_t::value && + std::is_convertible().error()), + err_t>::value> * = nullptr> +constexpr Ret and_then_impl(Exp &&exp, F &&f) { return exp.has_value() ? detail::invoke(std::forward(f)) : Ret(unexpect, std::forward(exp).error()); } -#else -template struct TC; -template (), - *std::declval())), - detail::enable_if_t>::value> * = nullptr> -auto and_then_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() - ? detail::invoke(std::forward(f), *std::forward(exp)) - : Ret(unexpect, std::forward(exp).error()); -} - -template ())), - detail::enable_if_t>::value> * = nullptr> -constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { - static_assert(detail::is_expected::value, "F must return an expected"); - - return exp.has_value() ? detail::invoke(std::forward(f)) - : Ret(unexpect, std::forward(exp).error()); -} -#endif #ifdef TL_EXPECTED_CXX14 template +constexpr auto hasAndThen_impl(int, Exp&& e, F&& f) + -> decltype(std::forward(e).and_then(std::forward(f)), true) { + return true; +} + +template +constexpr bool hasAndThen_impl(long, Exp&&, F&&) { return false; } + +template +constexpr bool hasAndThen(Exp&& e, F&& f) { + return hasAndThen_impl(42, std::forward(e), std::forward(f)); +} + +TEST_CASE("and_then is SFINAE-friendly", "[extensions.and_then.sfinae]") { + auto succeed = [](int) { return tl::expected(21 * 2); }; + + { + tl::expected e = 21; + STATIC_REQUIRE(hasAndThen(e, succeed)); + STATIC_REQUIRE(hasAndThen(std::move(e), succeed)); + STATIC_REQUIRE(hasAndThen(e, std::move(succeed))); + STATIC_REQUIRE(hasAndThen(std::move(e), std::move(succeed))); + } + { + const tl::expected ce = 21; + STATIC_REQUIRE(hasAndThen(ce, succeed)); + STATIC_REQUIRE(hasAndThen(std::move(ce), succeed)); + STATIC_REQUIRE(hasAndThen(ce, std::move(succeed))); + STATIC_REQUIRE(hasAndThen(std::move(ce), std::move(succeed))); + } + { + tl::expected e = 21; + STATIC_REQUIRE(!hasAndThen(e, 42)); + + auto wrongParamType = [](char *) { return tl::expected(); }; + STATIC_REQUIRE(!hasAndThen(e, wrongParamType)); + + auto wrongParamType2 = []() { return tl::expected(); }; + STATIC_REQUIRE(!hasAndThen(e, wrongParamType2)); + + auto wrongReturnType = [](int) { return 21; }; + STATIC_REQUIRE(!hasAndThen(e, wrongReturnType)); + + auto wrongReturnType2 = [](int) { return tl::expected(); }; + STATIC_REQUIRE(!hasAndThen(e, wrongReturnType2)); + + auto voidReturnType = [](int) {}; + STATIC_REQUIRE(!hasAndThen(e, voidReturnType)); + + auto voidReturnWrongParam = [](char *) {}; + STATIC_REQUIRE(!hasAndThen(e, voidReturnWrongParam)); + } + { + tl::expected e = 21; + const tl::expected ce = 21; + + auto lvalueParam = [](int&) { return tl::expected(); }; + STATIC_REQUIRE(hasAndThen(e, lvalueParam)); + STATIC_REQUIRE(!hasAndThen(std::move(e), lvalueParam)); + STATIC_REQUIRE(!hasAndThen(ce, lvalueParam)); + STATIC_REQUIRE(!hasAndThen(std::move(ce), lvalueParam)); + + auto rvalueParam = [](int&&) { return tl::expected(); }; + STATIC_REQUIRE(!hasAndThen(e, rvalueParam)); + STATIC_REQUIRE(hasAndThen(std::move(e), rvalueParam)); + STATIC_REQUIRE(!hasAndThen(ce, rvalueParam)); + STATIC_REQUIRE(!hasAndThen(std::move(ce), rvalueParam)); + } +} + TEST_CASE("And then extensions", "[extensions.and_then]") { auto succeed = [](int a) { return tl::expected(21 * 2); }; auto fail = [](int a) { return tl::expected(tl::unexpect, 17); }; From 103bfdb03a25bf35a9edb4ed25e6d7eddd64bc6c Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Sun, 2 Jan 2022 16:37:17 -0500 Subject: [PATCH 4/5] Add some simple tests for expected::map. --- tests/extensions.cpp | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/extensions.cpp b/tests/extensions.cpp index c6c2d0c..df6f89c 100644 --- a/tests/extensions.cpp +++ b/tests/extensions.cpp @@ -141,6 +141,43 @@ TEST_CASE("Map extensions", "[extensions.map]") { } } +TEST_CASE("Map on expected", "[extensions.map.void]") { + { + tl::expected a; + REQUIRE(a.has_value()); + bool called = false; + tl::expected b = a.map([&](){ called = true; }); + REQUIRE(called); + REQUIRE(b.has_value()); + } + { + tl::expected a = tl::make_unexpected(42); + REQUIRE(!a.has_value()); + bool called = false; + tl::expected b = a.map([&](){ called = true; }); + REQUIRE(!called); + REQUIRE(!b.has_value()); + REQUIRE(b.error() == 42); + } + { + tl::expected a; + REQUIRE(a.has_value()); + bool called = false; + tl::expected b = a.map([&](){ called = true; return 3; }); + REQUIRE(called); + REQUIRE(b.value() == 3); + } + { + tl::expected a = tl::make_unexpected(42); + REQUIRE(!a.has_value()); + bool called = false; + tl::expected b = a.map([&](){ called = true; return 3; }); + REQUIRE(!called); + REQUIRE(!b.has_value()); + REQUIRE(b.error() == 42); + } +} + TEST_CASE("Map error extensions", "[extensions.map_error]") { auto mul2 = [](int a) { return a * 2; }; auto ret_void = [](int a) {}; From fa31642e9c01010840d48404db8882f22540ad00 Mon Sep 17 00:00:00 2001 From: Arthur O'Dwyer Date: Sun, 2 Jan 2022 16:44:31 -0500 Subject: [PATCH 5/5] SFINAE-friendly `map` --- include/tl/expected.hpp | 119 ++++++++-------------------------------- tests/extensions.cpp | 80 +++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 95 deletions(-) diff --git a/include/tl/expected.hpp b/include/tl/expected.hpp index 70939d0..b52a501 100644 --- a/include/tl/expected.hpp +++ b/include/tl/expected.hpp @@ -1206,7 +1206,6 @@ template class bad_expected_access : public std::exception { namespace detail { template using exp_t = typename detail::decay_t::value_type; template using err_t = typename detail::decay_t::error_type; -template using ret_t = expected>; template >::value> * = nullptr, @@ -1232,115 +1231,45 @@ constexpr Ret and_then_impl(Exp &&exp, F &&f) { : Ret(unexpect, std::forward(exp).error()); } -#ifdef TL_EXPECTED_CXX14 -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_impl(Exp &&exp, F &&f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> -auto map_impl(Exp &&exp, F &&f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return result(); - } - - return result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> -constexpr auto map_impl(Exp &&exp, F &&f) { - using result = ret_t>; - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); -} - -template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> -auto map_impl(Exp &&exp, F &&f) { - using result = expected>; - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return result(); - } - - return result(unexpect, std::forward(exp).error()); -} -#else template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> - -constexpr auto map_impl(Exp &&exp, F &&f) - -> ret_t> { - using result = ret_t>; - - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); + class Ret = detail::decay_t(), *std::declval()))>, + detail::enable_if_t::value> * = nullptr, + class Result = expected>> +constexpr Result map_impl(Exp &&exp, F &&f) { + return exp.has_value() ? Result(detail::invoke(std::forward(f), *std::forward(exp))) + : Result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval(), - *std::declval())), - detail::enable_if_t::value> * = nullptr> - -auto map_impl(Exp &&exp, F &&f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f), *std::forward(exp)); - return {}; - } - - return unexpected>(std::forward(exp).error()); + class Ret = detail::decay_t(), *std::declval()))>, + detail::enable_if_t::value> * = nullptr, + class Result = expected>> +constexpr Result map_impl(Exp &&exp, F &&f) { + return exp.has_value() ? (detail::invoke(std::forward(f), *std::forward(exp)), Result()) + : Result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> - -constexpr auto map_impl(Exp &&exp, F &&f) - -> ret_t> { - using result = ret_t>; - - return exp.has_value() ? result(detail::invoke(std::forward(f))) - : result(unexpect, std::forward(exp).error()); + class Ret = detail::decay_t()))>, + detail::enable_if_t::value> * = nullptr, + class Result = expected>> +constexpr Result map_impl(Exp &&exp, F &&f) { + return exp.has_value() ? Result(detail::invoke(std::forward(f))) + : Result(unexpect, std::forward(exp).error()); } template >::value> * = nullptr, - class Ret = decltype(detail::invoke(std::declval())), - detail::enable_if_t::value> * = nullptr> - -auto map_impl(Exp &&exp, F &&f) -> expected> { - if (exp.has_value()) { - detail::invoke(std::forward(f)); - return {}; - } - - return unexpected>(std::forward(exp).error()); + class Ret = detail::decay_t()))>, + detail::enable_if_t::value> * = nullptr, + class Result = expected>> +constexpr Result map_impl(Exp &&exp, F &&f) { + return exp.has_value() ? (detail::invoke(std::forward(f)), Result()) + : Result(unexpect, std::forward(exp).error()); } -#endif #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) diff --git a/tests/extensions.cpp b/tests/extensions.cpp index df6f89c..527a978 100644 --- a/tests/extensions.cpp +++ b/tests/extensions.cpp @@ -7,6 +7,86 @@ constexpr bool TOKENPASTE2(rqure, __LINE__) = e; \ REQUIRE(e); +template +constexpr auto hasMap_impl(int, Exp&& e, F&& f) + -> decltype(std::forward(e).map(std::forward(f)), true) { + return true; +} + +template +constexpr bool hasMap_impl(long, Exp&&, F&&) { return false; } + +template +constexpr bool hasMap(Exp&& e, F&& f) { + return hasMap_impl(42, std::forward(e), std::forward(f)); +} + +TEST_CASE("map is SFINAE-friendly", "[extensions.map.sfinae]") { + auto mul2 = [](int a) { return a * 2; }; + auto ret_void = [](int a) {}; + + { + tl::expected e = 21; + STATIC_REQUIRE(hasMap(e, mul2)); + STATIC_REQUIRE(hasMap(std::move(e), mul2)); + STATIC_REQUIRE(hasMap(e, std::move(mul2))); + STATIC_REQUIRE(hasMap(std::move(e), std::move(mul2))); + } + { + const tl::expected ce = 21; + STATIC_REQUIRE(hasMap(ce, mul2)); + STATIC_REQUIRE(hasMap(std::move(ce), mul2)); + STATIC_REQUIRE(hasMap(ce, std::move(mul2))); + STATIC_REQUIRE(hasMap(std::move(ce), std::move(mul2))); + } + { + tl::expected ev; + tl::expected ei = 21; + + STATIC_REQUIRE(!hasMap(ev, 42)); + STATIC_REQUIRE(!hasMap(ei, 42)); + + STATIC_REQUIRE(!hasMap(ev, mul2)); + STATIC_REQUIRE(hasMap(ei, mul2)); + + auto wrongParamType = [](char *) { return 21; }; + STATIC_REQUIRE(!hasMap(ev, wrongParamType)); + STATIC_REQUIRE(!hasMap(ei, wrongParamType)); + + auto voidParamType = []() { return 21; }; + STATIC_REQUIRE(hasMap(ev, voidParamType)); + STATIC_REQUIRE(!hasMap(ei, voidParamType)); + + auto voidReturnVoidParam = []() {}; + STATIC_REQUIRE(hasMap(ev, voidReturnVoidParam)); + STATIC_REQUIRE(!hasMap(ei, voidReturnVoidParam)); + + auto voidReturnIntParam = [](int) {}; + STATIC_REQUIRE(!hasMap(ev, voidReturnIntParam)); + STATIC_REQUIRE(hasMap(ei, voidReturnIntParam)); + + auto voidReturnWrongParam = [](char *) {}; + STATIC_REQUIRE(!hasMap(ev, voidReturnWrongParam)); + STATIC_REQUIRE(!hasMap(ei, voidReturnWrongParam)); + } + { + tl::expected e = 21; + const tl::expected ce = 21; + + auto lvalueParam = [](int&) { return 21; }; + STATIC_REQUIRE(hasMap(e, lvalueParam)); + STATIC_REQUIRE(!hasMap(std::move(e), lvalueParam)); + STATIC_REQUIRE(!hasMap(ce, lvalueParam)); + STATIC_REQUIRE(!hasMap(std::move(ce), lvalueParam)); + + auto rvalueParam = [](int&&) { return 21; }; + STATIC_REQUIRE(!hasMap(e, rvalueParam)); + STATIC_REQUIRE(hasMap(std::move(e), rvalueParam)); + STATIC_REQUIRE(!hasMap(ce, rvalueParam)); + STATIC_REQUIRE(!hasMap(std::move(ce), rvalueParam)); + } +} + TEST_CASE("Map extensions", "[extensions.map]") { auto mul2 = [](int a) { return a * 2; }; auto ret_void = [](int a) {};