From 6e10df258ab9a645fbedd8ee146a26371db07207 Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Thu, 2 Nov 2017 07:16:19 +0000 Subject: [PATCH] Start work on conditionally deleting special members --- expected.hpp | 716 ++++++++++++++++++++++++++------------------------- 1 file changed, 364 insertions(+), 352 deletions(-) diff --git a/expected.hpp b/expected.hpp index d212a12..405a2dd 100644 --- a/expected.hpp +++ b/expected.hpp @@ -32,8 +32,20 @@ #define TL_EXPECTED_GCC54 #endif -#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9) +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions #define TL_EXPECTED_NO_CONSTRR + +//GCC < 5 doesn't support some standard C++11 type traits +#define IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) std::has_trivial_copy_constructor::value +#define IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported +#define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#else +#define IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) std::is_trivially_copy_constructible::value +#define IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::is_trivially_copy_assignable::value +#define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value #endif #if __cplusplus > 201103L @@ -372,190 +384,356 @@ template struct expected_storage_base { }; }; -// 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 && +// This base class provides some handy member functions which can be used in further derived classes + template struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + void hard_reset() noexcept { + get().~T(); + this->m_has_value = false; + } + + template void construct(Args &&... args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_value = true; + } + + template void construct_error(Args &&... args) noexcept { + new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); + this->m_has_value = false; + } + + // 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_operations_base &assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } + else { + 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_operations_base &assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } + else { + 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_operations_base &assign(const expected_operations_base &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + + try { + construct(rhs.get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } + } + else { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + expected_operations_base &assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + ::new (valptr()) T(*std::move(rhs)); + this->m_has_val = true; + } + else { + assign_common(rhs); + } + } + + template ::value> + * = nullptr> + expected_operations_base &assign(expected_operations_base &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + try { + construct(std::move(rhs).get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } + } + else { + assign_common(rhs); + } + } + + // The common part of move/copy assigning + template void assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + get() = std::forward(rhs).get(); + } else { + get().~T(); + construct_err(std::forward(rhs).geterr()); + } + } else { + if (!rhs.m_has_val) { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_value; } + + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR T &geterr() & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR const T &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR T &&geterr() && { std::move(this->m_unexpect); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&geterr() const && { return std::move(this->m_unexpect); } +#endif +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T is trivially copy constructible + template + struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T is not trivially copy constructible + template + struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base &rhs) { + if (rhs.has_value()) { + this->construct(rhs.get()); + } else { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it doesn't implement an analogue to std::is_trivially_move_constructible. We have to make do with a non-trivial move constructor even if T is trivially move constructible +#ifndef TL_EXPECTED_GCC49 + template ::value> + struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; +}; +#else + template + struct expected_move_base; + #endif + template struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base &rhs) = default; + + expected_move_base(expected_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) { + if (rhs.has_value()) { + this->construct(std::move(rhs.get())); + } else { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator + template + struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + + template + struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { + this->assign(rhs); + } + expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it doesn't implement an analogue to std::is_trivially_move_assignable. We have to make do with a non-trivial move assignment operator even if T is trivially move assignable +#ifndef TL_EXPECTED_GCC49 + template ::value && std::is_trivially_move_constructible::value && std::is_trivially_move_assignable::value> + struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else + template + struct expected_move_assign_base; +#endif + + template + struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base(expected_move_assign_base &&rhs) = default; + expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + } + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) = default; +}; + +// expected_delete_ctor_base will conditionally delete copy and move constructors depending on whether T is copy/move constructible + template ::value && std::is_copy_constructible::value), + bool EnableMove = (std::is_move_constructible::value && std::is_move_constructible::value)> +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move constructors depending on whether T is copy/move constructible + assignable +template ::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_copy_assignable::value && + std::is_copy_assignable::value), + bool EnableMove = (std::is_move_constructible::value && + 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; + std::is_move_assignable::value)> +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_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(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_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_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(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_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_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(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; -}; - -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; +template struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; }; // This is needed to be able to construct the expected_default_ctor_base which @@ -597,173 +775,6 @@ template struct expected_default_ctor_base { 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); - } - } - - 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)); - } - } - - expected_impl &operator=(const expected_impl &rhs) { return assign(rhs); } - - expected_impl &operator=(expected_impl &&rhs) { - return assign(std::move(rhs)); - } - - // 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; - } - - 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 template class bad_expected_access : public std::exception { @@ -791,8 +802,9 @@ private: /// has been destroyed. The initialization state of the contained object is /// tracked by the expected object. template -class expected : private detail::expected_impl, - private detail::expected_copy_move_base, +class expected : private detail::expected_move_assign, + 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>::value, @@ -822,11 +834,11 @@ public: #if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ !defined(TL_EXPECTED_GCC54) /// \group and_then - /// Carries out some operation which returns an optional on the stored object + /// Carries out some operation which returns an expected on the stored object /// if there is one. \requires `std::invoke(std::forward(f), value())` - /// returns a `std::optional` for some `U`. \returns Let `U` be the result + /// returns a `std::expected` for some `U`. \returns Let `U` be the result /// of `std::invoke(std::forward(f), value())`. Returns a - /// `std::optional`. The return value is empty if `*this` is empty, + /// `std::expected`. The return value is empty if `*this` is empty, /// otherwise the return value of `std::invoke(std::forward(f), value())` /// is returned. \group and_then \synopsis template \nconstexpr auto /// and_then(F &&f) &; @@ -876,11 +888,11 @@ public: #else /// \group and_then - /// Carries out some operation which returns an optional on the stored object + /// Carries out some operation which returns an expected on the stored object /// if there is one. \requires `std::invoke(std::forward(f), value())` - /// returns a `std::optional` for some `U`. \returns Let `U` be the result + /// returns a `std::expected` for some `U`. \returns Let `U` be the result /// of `std::invoke(std::forward(f), value())`. Returns a - /// `std::optional`. The return value is empty if `*this` is empty, + /// `std::expected`. The return value is empty if `*this` is empty, /// otherwise the return value of `std::invoke(std::forward(f), value())` /// is returned. \group and_then \synopsis template \nconstexpr auto /// and_then(F &&f) &;