diff --git a/expected.hpp b/expected.hpp index e09698c..57b7827 100644 --- a/expected.hpp +++ b/expected.hpp @@ -53,6 +53,7 @@ namespace tl { template class expected; +/// \exclude namespace detail { template using enable_if_t = typename std::enable_if::type; @@ -110,69 +111,98 @@ using invoke_result_t = typename invoke_result::type; #ifndef TL_IN_PLACE_MONOSTATE_DEFINED #define TL_IN_PLACE_MONOSTATE_DEFINED -/// \brief Used to represent an optional with no data; essentially a bool +/// \brief Used to represent an expected with no data class monostate {}; -/// \brief A tag type to tell optional to construct its value in-place +/// \brief A tag type to tell expected to construct its value in-place struct in_place_t { explicit in_place_t() = default; }; -/// \brief A tag to tell optional to construct its value in-place +/// \brief A tag to tell expected to construct its value in-place static constexpr in_place_t in_place{}; #endif +/// Used as a wrapper to store the unexpected value template class unexpected { public: static_assert(!std::is_same::value, "E must not be void"); + unexpected() = delete; constexpr explicit unexpected(const E &e) : m_val(e) {} constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} + /// \returns the contained value + /// \group unexpected_value constexpr const E &value() const & { return m_val; } + /// \group unexpected_value constexpr E &value() & { return m_val; } + /// \group unexpected_value constexpr E &&value() && { return std::move(m_val); } + /// \group unexpected_value constexpr E const &&value() const && { return std::move(m_val); } private: E m_val; }; +/// \brief Compares two unexpected objects +/// \details Simply compares lhs.value() to rhs.value() +/// \group unexpected_relop template constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { return lhs.value() == rhs.value(); } +/// \group unexpected_relop template constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() != rhs.value(); } +/// \group unexpected_relop template constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { return lhs.value() < rhs.value(); } +/// \group unexpected_relop template constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() <= rhs.value(); } +/// \group unexpected_relop template constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { return lhs.value() > rhs.value(); } +/// \group unexpected_relop template constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() >= rhs.value(); } +/// Create an `unexpected` from `e`, deducing the return type +/// +/// *Example:* +/// auto e1 = tl::make_unexpected(42); +/// unexpected e2 (42); //same semantics template unexpected make_unexpected(E &&e) { return unexpected(std::forward(e)); } +/// \brief A tag type to tell expected to construct the unexpected value struct unexpect_t { unexpect_t() = default; }; +/// \brief A tag to tell expected to construct the unexpected value static constexpr unexpect_t unexpect{}; +/// \exclude namespace detail { +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` template ::value, bool = std::is_trivially_destructible::value> struct expected_storage_base { @@ -218,6 +248,8 @@ struct expected_storage_base { }; }; +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. template struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} @@ -255,6 +287,7 @@ template struct expected_storage_base { }; }; +// T is trivial, E is not. template struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} @@ -297,6 +330,7 @@ template struct expected_storage_base { }; }; +// E is trivial, T is not. template struct expected_storage_base { constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} @@ -338,110 +372,396 @@ template struct expected_storage_base { }; }; -template struct expected_storage_base { - constexpr expected_storage_base() : m_val(), m_has_val(true) {} - - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} - - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} - - ~expected_storage_base() = default; - - bool m_has_val; - struct dummy {}; - union { - dummy m_val; - unexpected m_unexpect; - }; +// expected_copy_move_base is used to conditionally delete the move/copy +// constructors/assignment operators depending on the traits of T and E. + // TODO these could be reduced a bit by splitting construction and assignment into different bases +template < + class T, class E, + bool EnableCopy = (std::is_copy_constructible::value && + std::is_copy_constructible::value), + bool EnableMove = (std::is_move_constructible::value && + std::is_move_constructible::value), + bool EnableCopyAssign = (std::is_copy_assignable::value && + std::is_copy_assignable::value && + std::is_copy_constructible::value && + std::is_copy_constructible::value && + std::is_nothrow_move_constructible::value), + bool EnableMoveAssign = (std::is_move_constructible::value && + std::is_move_assignable::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_assignable::value)> +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; }; -template struct expected_storage_base { - constexpr expected_storage_base() : m_val(), m_has_val(true) {} +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; - template ::value> * = - nullptr> - constexpr explicit expected_storage_base(unexpect_t, Args &&... args) - : m_unexpect(std::forward(args)...), m_has_val(false) {} +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; - template &, Args &&...>::value> * = nullptr> - constexpr explicit expected_storage_base(unexpect_t, - std::initializer_list il, - Args &&... args) - : m_unexpect(il, std::forward(args)...), m_has_val(false) {} +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; - ~expected_storage_base() { - if (m_has_val) { - m_val.~T(); +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = default; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = default; +}; + +template +struct expected_copy_move_base { + expected_copy_move_base() = default; + ~expected_copy_move_base() = default; + expected_copy_move_base(const expected_copy_move_base &) = delete; + expected_copy_move_base(expected_copy_move_base &&) noexcept = default; + expected_copy_move_base &operator=(const expected_copy_move_base &) = delete; + expected_copy_move_base & + operator=(expected_copy_move_base &&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag { + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template ::value> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// expected_impl provides indirection for the copy/move constructor/assignment +// operators, so those in expected itself can be declared `=default`. This +// allows the base classes for conditionally deleting those special member +// functions to work. +template +struct expected_impl : protected expected_storage_base { + T *valptr() { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + T &val() { return this->m_val; } + unexpected &err() { return this->m_unexpect; } + const T &val() const { return this->m_val; } + const unexpected &err() const { return this->m_unexpect; } + + constexpr const T &operator*() const & { return val(); } + constexpr T &operator*() & { return val(); } + constexpr const T &&operator*() const && { return std::move(val()); } + constexpr T &&operator*() && { return std::move(val()); } + + using storage_base = detail::expected_storage_base; + using storage_base::storage_base; + + constexpr expected_impl() = default; + + constexpr expected_impl(const expected_impl &rhs) { + if (this->m_has_val) { + ::new (valptr()) T(*rhs); + } else { + ::new (errptr()) unexpected(rhs.m_unexpect); } } - bool m_has_val; - struct dummy {}; - union { - dummy m_val; - unexpected m_unexpect; - }; -}; + constexpr expected_impl(expected_impl &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_constructible::value) { + if (rhs.m_has_val) { + ::new (valptr()) T(std::move(rhs.m_val)); + } else { + ::new (errptr()) unexpected(std::move(rhs.m_unexpect)); + } + } -// TODO, conditionally delete things -template struct expected_ctor_base {}; -template ::value && - std::is_copy_assignable::value && - std::is_copy_constructible::value && - std::is_copy_constructible::value && - std::is_nothrow_move_constructible::value), - bool = (std::is_move_constructible::value && - std::is_move_assignable::value && - std::is_nothrow_move_constructible::value && - std::is_nothrow_move_assignable::value)> -struct expected_assign_base { - expected_assign_base() = default; - ~expected_assign_base() = default; - expected_assign_base(const expected_assign_base &) = default; - expected_assign_base(expected_assign_base &&) noexcept = default; - expected_assign_base &operator=(const expected_assign_base &) = default; - expected_assign_base &operator=(expected_assign_base &&) noexcept = default; -}; + expected_impl &operator=(const expected_impl &rhs) { return assign(rhs); } -template struct expected_assign_base { - expected_assign_base() = default; - ~expected_assign_base() = default; - expected_assign_base(const expected_assign_base &) = default; - expected_assign_base(expected_assign_base &&) noexcept = default; - expected_assign_base &operator=(const expected_assign_base &) = default; - expected_assign_base &operator=(expected_assign_base &&) noexcept = delete; -}; + expected_impl &operator=(expected_impl &&rhs) { + return assign(std::move(rhs)); + } -template struct expected_assign_base { - expected_assign_base() = default; - ~expected_assign_base() = default; - expected_assign_base(const expected_assign_base &) = default; - expected_assign_base(expected_assign_base &&) noexcept = default; - expected_assign_base &operator=(const expected_assign_base &) = delete; - expected_assign_base &operator=(expected_assign_base &&) noexcept = default; -}; + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template ::value> + * = nullptr> + expected_impl &assign(const expected_impl &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + err().~unexpected(); + ::new (valptr()) T(*rhs); + this->m_has_val = true; + return *this; + } -template struct expected_assign_base { - expected_assign_base() = default; - ~expected_assign_base() = default; - expected_assign_base(const expected_assign_base &) = default; - expected_assign_base(expected_assign_base &&) noexcept = default; - expected_assign_base &operator=(const expected_assign_base &) = delete; - expected_assign_base &operator=(expected_assign_base &&) noexcept = delete; + return assign_common(rhs); + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template ::value && + std::is_nothrow_move_constructible::value> + * = nullptr> + expected_impl &assign(const expected_impl &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = *rhs; + err().~unexpected(); + ::new (valptr()) T(std::move(tmp)); + this->m_has_val = true; + return *this; + } + + return assign_common(rhs); + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template ::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + expected_impl &assign(const expected_impl &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(err()); + err().~unexpected(); + + try { + ::new (valptr()) T(*rhs); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + return *this; + } + + return assign_common(rhs); + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + expected_impl &assign(expected_impl &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + err().~unexpected(); + ::new (valptr()) T(*std::move(rhs)); + this->m_has_val = true; + return *this; + } + + return assign_common(rhs); + } + + template ::value> + * = nullptr> + expected_impl &assign(expected_impl &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(err()); + err().~unexpected(); + try { + ::new (valptr()) T(*std::move(rhs)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } + + return *this; + } + + return assign_common(rhs); + } + + // The common part of move/copy assigning + template expected_impl &assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + val() = *std::forward(rhs); + } else { + val().~T(); + ::new (errptr()) unexpected(std::forward(rhs).err()); + } + } else { + if (!rhs.m_has_val) { + err() = std::forward(rhs).err(); + } + } + + return *this; + } }; } // namespace detail @@ -462,9 +782,17 @@ private: 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_storage_base, - private detail::expected_assign_base { +class expected : private detail::expected_impl, + private detail::expected_copy_move_base, + private detail::expected_default_ctor_base { static_assert(!std::is_reference::value, "T must not be a reference"); static_assert(!std::is_same>::value, "T must not be in_place_t"); @@ -482,7 +810,8 @@ class expected : private detail::expected_storage_base, const T &val() const { return this->m_val; } const unexpected &err() const { return this->m_unexpect; } - using storage_base = detail::expected_storage_base; + using impl_base = detail::expected_impl; + using ctor_base = detail::expected_default_ctor_base; public: typedef T value_type; @@ -729,18 +1058,24 @@ public: #endif constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + constexpr expected &operator=(const expected &rhs) = default; + constexpr expected &operator=(expected &&rhs) = default; template ::value> * = nullptr> constexpr expected(in_place_t, Args &&... args) - : storage_base(in_place, std::forward(args)...) {} + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} template &, Args &&...>::value> * = nullptr> constexpr expected(in_place_t, std::initializer_list il, Args &&... args) - : storage_base(in_place, il, std::forward(args)...) {} + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} template ::value> * = @@ -748,7 +1083,8 @@ public: detail::enable_if_t::value> * = nullptr> explicit constexpr expected(unexpected const &e) - : storage_base(unexpect, e.value()) {} + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} template < class G = E, @@ -756,7 +1092,8 @@ public: nullptr, detail::enable_if_t::value> * = nullptr> constexpr expected(unexpected const &e) - : storage_base(unexpect, e.value()) {} + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} template < class G = E, @@ -764,7 +1101,8 @@ public: detail::enable_if_t::value> * = nullptr> explicit constexpr expected(unexpected &&e) noexcept( std::is_nothrow_constructible::value) - : storage_base(unexpect, std::move(e.value())) {} + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} template < class G = E, @@ -772,46 +1110,31 @@ public: detail::enable_if_t::value> * = nullptr> constexpr expected(unexpected &&e) noexcept( std::is_nothrow_constructible::value) - : storage_base(unexpect, std::move(e.value())) {} + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} template ::value> * = nullptr> constexpr explicit expected(unexpect_t, Args &&... args) - : storage_base(unexpect, std::forward(args)...) {} + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} template &, Args &&...>::value> * = nullptr> constexpr explicit expected(unexpect_t, std::initializer_list il, Args &&... args) - : storage_base(unexpect, il, std::forward(args)...) {} - - constexpr expected(const expected &rhs) { - if (rhs.has_value()) { - ::new (valptr()) T(*rhs); - } else { - ::new (errptr()) unexpected_type(unexpected(rhs.error())); - } - } - - // TODO SFINAE - constexpr expected(expected &&rhs) noexcept( - std::is_nothrow_move_constructible::value - &&std::is_nothrow_move_constructible::value) { - if (rhs.has_value()) { - ::new (valptr()) T(std::move(*rhs)); - } else { - ::new (errptr()) unexpected_type(unexpected(std::move(rhs.error()))); - } - } + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} // TODO SFINAE template ::value || !std::is_convertible::value)> * = nullptr> - explicit constexpr expected(const expected &rhs) { + explicit constexpr expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { ::new (valptr()) T(*rhs); } else { @@ -824,7 +1147,8 @@ public: class U, class G, detail::enable_if_t<(!std::is_convertible::value || !std::is_convertible::value)> * = nullptr> - explicit constexpr expected(const expected &rhs) { + explicit constexpr expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { ::new (valptr()) T(*rhs); } else { @@ -837,7 +1161,8 @@ public: class U, class G, detail::enable_if_t<(std::is_convertible::value || std::is_convertible::value)> * = nullptr> - constexpr expected(expected &&rhs) { + constexpr expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { if (rhs.has_value()) { ::new (valptr()) T(std::move(*rhs)); } else { @@ -855,11 +1180,6 @@ public: std::is_convertible::value> * = nullptr> constexpr expected(U &&v) : expected(in_place, std::forward(v)) {} - // TODO conditionally delete - expected &operator=(const expected &rhs) { return assign(rhs); } - - expected &operator=(expected &&rhs) { return assign(std::move(rhs)); } - template < class U = T, detail::enable_if_t< @@ -1079,110 +1399,6 @@ public: } private: - template ::value> - * = nullptr> - expected &assign(const expected &rhs) noexcept { - if (!has_value() && rhs.has_value()) { - err().~unexpected(); - ::new (valptr()) T(*rhs); - this->m_has_val = true; - return *this; - } - - return assign_common(rhs); - } - - template ::value && - std::is_nothrow_move_constructible::value> - * = nullptr> - expected &assign(const expected &rhs) noexcept { - if (!has_value() && rhs.has_value()) { - T tmp = *rhs; - err().~unexpected(); - ::new (valptr()) T(std::move(tmp)); - this->m_has_val = true; - return *this; - } - - return assign_common(rhs); - } - - template ::value && - !std::is_nothrow_move_constructible::value> - * = nullptr> - expected &assign(const expected &rhs) { - if (!has_value() && rhs.has_value()) { - auto tmp = std::move(err()); - err().~unexpected(); - - try { - ::new (valptr()) T(*rhs); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } - this->m_has_val = true; - return *this; - } - - return assign_common(rhs); - } - - template ::value> - * = nullptr> - expected &assign(expected &&rhs) noexcept { - if (!has_value() && rhs.has_value()) { - err().~unexpected(); - ::new (valptr()) T(*std::move(rhs)); - this->m_has_val = true; - return *this; - } - - return assign_common(rhs); - } - - template ::value> - * = nullptr> - expected &assign(expected &&rhs) { - if (!has_value() && rhs.has_value()) { - auto tmp = std::move(err()); - err().~unexpected(); - try { - ::new (valptr()) T(*std::move(rhs)); - this->m_has_val = true; - } catch (...) { - err() = std::move(tmp); - throw; - } - - return *this; - } - - return assign_common(rhs); - } - - template expected &assign_common(Rhs &&rhs) { - if (has_value()) { - if (rhs.has_value()) { - val() = *std::forward(rhs); - } else { - val().~T(); - ::new (errptr()) unexpected(std::forward(rhs).err()); - } - } else { - if (!rhs.has_value()) { - err() = std::forward(rhs).err(); - } - } - - return *this; - } }; namespace detail { diff --git a/tests/assignment.cpp b/tests/assignment.cpp index ac0514f..6d22d1d 100644 --- a/tests/assignment.cpp +++ b/tests/assignment.cpp @@ -2,75 +2,74 @@ #include "expected.hpp" TEST_CASE("Simple assignment", "[assignment.simple]") { - tl::expected e1 = 42; - tl::expected e2 = 17; - tl::expected e3 = 21; - tl::expected e4 = tl::make_unexpected(42); - tl::expected e5 = tl::make_unexpected(17); - tl::expected e6 = tl::make_unexpected(21); + tl::expected e1 = 42; + tl::expected e2 = 17; + tl::expected e3 = 21; + tl::expected e4 = tl::make_unexpected(42); + tl::expected e5 = tl::make_unexpected(17); + tl::expected e6 = tl::make_unexpected(21); - e1 = e2; - REQUIRE(e1); - REQUIRE(*e1 == 17); - REQUIRE(e2); - REQUIRE(*e2 == 17); + e1 = e2; + REQUIRE(e1); + REQUIRE(*e1 == 17); + REQUIRE(e2); + REQUIRE(*e2 == 17); - e1 = std::move(e2); - REQUIRE(e1); - REQUIRE(*e1 == 17); - REQUIRE(e2); - REQUIRE(*e2 == 17); + e1 = std::move(e2); + REQUIRE(e1); + REQUIRE(*e1 == 17); + REQUIRE(e2); + REQUIRE(*e2 == 17); - e1 = 42; - REQUIRE(e1); - REQUIRE(*e1 == 42); + e1 = 42; + REQUIRE(e1); + REQUIRE(*e1 == 42); - auto unex = tl::make_unexpected(12); - e1 = unex; - REQUIRE(!e1); - REQUIRE(e1.error() == 12); + auto unex = tl::make_unexpected(12); + e1 = unex; + REQUIRE(!e1); + REQUIRE(e1.error() == 12); - e1 = tl::make_unexpected(42); - REQUIRE(!e1); - REQUIRE(e1.error() == 42); + e1 = tl::make_unexpected(42); + REQUIRE(!e1); + REQUIRE(e1.error() == 42); - e1 = e3; - REQUIRE(e1); - REQUIRE(*e1 == 21); + e1 = e3; + REQUIRE(e1); + REQUIRE(*e1 == 21); - e4 = e5; - REQUIRE(!e4); - REQUIRE(e4.error() == 17); + e4 = e5; + REQUIRE(!e4); + REQUIRE(e4.error() == 17); - e4 = std::move(e6); - REQUIRE(!e4); - REQUIRE(e4.error() == 21); - - e4 = e1; - REQUIRE(e4); - REQUIRE(*e4 == 21); + e4 = std::move(e6); + REQUIRE(!e4); + REQUIRE(e4.error() == 21); + e4 = e1; + REQUIRE(e4); + REQUIRE(*e4 == 21); } TEST_CASE("Assignment deletion", "[assignment.deletion]") { - struct has_all { - has_all() = default; - has_all(const has_all&) = default; - has_all(has_all&&) noexcept = default; - has_all& operator=(const has_all&) = default; - }; + struct has_all { + has_all() = default; + has_all(const has_all &) = default; + has_all(has_all &&) noexcept = default; + has_all &operator=(const has_all &) = default; + }; - tl::expected e1 = {}; - tl::expected e2 = {}; - e1 = e2; + tl::expected e1 = {}; + tl::expected e2 = {}; + e1 = e2; - struct except_move { - except_move() = default; - except_move(const except_move&) = default; - except_move(except_move&&) noexcept(false) {}; - except_move& operator=(const except_move&) = default; - }; - tl::expected e3 = {}; - tl::expected e4 = {}; - e3 = e4; + struct except_move { + except_move() = default; + except_move(const except_move &) = default; + except_move(except_move &&) noexcept(false){}; + except_move &operator=(const except_move &) = default; + }; + tl::expected e3 = {}; + tl::expected e4 = {}; + // e3 = e4; should not compile }