From 468c403c919e8794ac89a36f18578a8edfdc609f Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Mon, 18 Dec 2017 15:57:49 +0000 Subject: [PATCH 1/5] Initial void support --- tests/bases.cpp | 16 +-- tests/constructors.cpp | 16 +-- tests/extensions.cpp | 16 +-- tl/expected.hpp | 271 +++++++++++++++++++++++++++++++---------- 4 files changed, 229 insertions(+), 90 deletions(-) diff --git a/tests/bases.cpp b/tests/bases.cpp index ae596e3..4f3fc80 100644 --- a/tests/bases.cpp +++ b/tests/bases.cpp @@ -127,8 +127,8 @@ TEST_CASE("Deletion", "[bases.deletion]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(std::is_trivially_move_constructible::value); REQUIRE(std::is_trivially_move_assignable::value); @@ -142,8 +142,8 @@ TEST_CASE("Deletion", "[bases.deletion]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(!std::is_trivially_move_constructible::value); REQUIRE(!std::is_trivially_move_assignable::value); @@ -157,8 +157,8 @@ TEST_CASE("Deletion", "[bases.deletion]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(!std::is_trivially_move_constructible::value); REQUIRE(!std::is_trivially_move_assignable::value); @@ -172,8 +172,8 @@ TEST_CASE("Deletion", "[bases.deletion]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(!std::is_trivially_move_constructible::value); REQUIRE(!std::is_trivially_move_assignable::value); diff --git a/tests/constructors.cpp b/tests/constructors.cpp index ad669eb..986b69b 100644 --- a/tests/constructors.cpp +++ b/tests/constructors.cpp @@ -68,8 +68,8 @@ TEST_CASE("Constructors", "[constructors]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(std::is_trivially_move_constructible::value); REQUIRE(std::is_trivially_move_assignable::value); @@ -83,8 +83,8 @@ TEST_CASE("Constructors", "[constructors]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(!std::is_trivially_move_constructible::value); REQUIRE(!std::is_trivially_move_assignable::value); @@ -98,8 +98,8 @@ TEST_CASE("Constructors", "[constructors]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(!std::is_trivially_move_constructible::value); REQUIRE(!std::is_trivially_move_assignable::value); @@ -113,8 +113,8 @@ TEST_CASE("Constructors", "[constructors]") { REQUIRE(std::is_move_constructible::value); REQUIRE(std::is_copy_assignable::value); REQUIRE(std::is_move_assignable::value); - REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))); - REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))); + REQUIRE(!IS_TRIVIALLY_COPY_CONSTRUCTIBLE(decltype(e))::value); + REQUIRE(!IS_TRIVIALLY_COPY_ASSIGNABLE(decltype(e))::value); # if !defined(TL_EXPECTED_GCC49) REQUIRE(!std::is_trivially_move_constructible::value); REQUIRE(!std::is_trivially_move_assignable::value); diff --git a/tests/extensions.cpp b/tests/extensions.cpp index ecd1c92..39cd46c 100644 --- a/tests/extensions.cpp +++ b/tests/extensions.cpp @@ -72,7 +72,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = e.map(ret_void); REQUIRE(ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -80,7 +80,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = e.map(ret_void); REQUIRE(ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -88,7 +88,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = std::move(e).map(ret_void); REQUIRE(ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -96,7 +96,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = std::move(e).map(ret_void); REQUIRE(ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -104,7 +104,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = e.map(ret_void); REQUIRE(!ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -112,7 +112,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = e.map(ret_void); REQUIRE(!ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -120,7 +120,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = std::move(e).map(ret_void); REQUIRE(!ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } { @@ -128,7 +128,7 @@ TEST_CASE("Map extensions", "[extensions.map]") { auto ret = std::move(e).map(ret_void); REQUIRE(!ret); STATIC_REQUIRE( - (std::is_same>::value)); + (std::is_same>::value)); } diff --git a/tl/expected.hpp b/tl/expected.hpp index 4a61b65..68c371e 100644 --- a/tl/expected.hpp +++ b/tl/expected.hpp @@ -49,22 +49,22 @@ // GCC < 5 doesn't support some standard C++11 type traits /// \exclude #define IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - std::has_trivial_copy_constructor::value + std::has_trivial_copy_constructor /// \exclude -#define IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value +#define IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign // This one will be different for GCC 5.7 if it's ever supported /// \exclude -#define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible #else /// \exclude #define IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ - std::is_trivially_copy_constructible::value + std::is_trivially_copy_constructible /// \exclude #define IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ - std::is_trivially_copy_assignable::value + std::is_trivially_copy_assignable /// \exclude -#define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#define IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible #endif #if __cplusplus > 201103L @@ -166,7 +166,8 @@ constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { /// *Example:* /// auto e1 = tl::make_unexpected(42); /// unexpected e2 (42); //same semantics -template unexpected::type> make_unexpected(E &&e) { +template +unexpected::type> make_unexpected(E &&e) { return unexpected::type>(std::forward(e)); } @@ -260,11 +261,21 @@ using expected_enable_from_other = detail::enable_if_t< !std::is_convertible &, T>::value && !std::is_convertible &&, T>::value>; + template + using is_void_or = conditional_t::value, std::true_type, U>; + + template + using is_copy_constructible_or_void = is_void_or>; + + template + using is_move_constructible_or_void = is_void_or>; + + } // namespace detail /// \exclude namespace detail { -struct no_init_t{}; +struct no_init_t {}; static constexpr no_init_t no_init{}; // Implements the storage of the values, and ensures that the destructor is @@ -446,17 +457,77 @@ template struct expected_storage_base { }; }; +// `T` is `void`, `E` is trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : 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; + }; +}; + +// `T` is `void`, `E` is not trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : 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() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + bool m_has_val; + struct dummy {}; + union { + dummy m_val; + unexpected m_unexpect; + }; +}; + // 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_val = false; - } - template void construct(Args &&... args) noexcept { new (std::addressof(this->m_val)) T(std::forward(args)...); this->m_has_val = true; @@ -597,9 +668,60 @@ struct expected_operations_base : expected_storage_base { #endif }; +// 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; + + template void construct() noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&... args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template + void assign(Rhs &&rhs) noexcept { + if (!this->m_has_val) { + if (rhs.m_has_val) { + geterr().~unexpected(); + construct(); + } + else { + geterr() = std::forward(rhs).geterr(); + } + } else { + if (!rhs.m_has_val) { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif +}; + // This class manages conditionally having a trivial copy constructor // This specialization is for when T and E are trivially copy constructible -template +template ::value && + IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> struct expected_copy_base : expected_operations_base { using expected_operations_base::expected_operations_base; }; @@ -610,8 +732,8 @@ 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) : - expected_operations_base(no_init) { + expected_copy_base(const expected_copy_base &rhs) + : expected_operations_base(no_init) { if (rhs.has_value()) { this->construct(rhs.get()); } else { @@ -631,8 +753,8 @@ struct expected_copy_base : expected_operations_base { // move constructible #ifndef TL_EXPECTED_GCC49 template ::value - && std::is_trivially_move_constructible::value> + bool = is_void_or>::value + &&std::is_trivially_move_constructible::value> struct expected_move_base : expected_copy_base { using expected_copy_base::expected_copy_base; }; @@ -647,8 +769,8 @@ struct expected_move_base : expected_copy_base { expected_move_base(const expected_move_base &rhs) = default; expected_move_base(expected_move_base &&rhs) noexcept( - std::is_nothrow_move_constructible::value) : - expected_copy_base(no_init) { + std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) { if (rhs.has_value()) { this->construct(std::move(rhs.get())); } else { @@ -660,13 +782,14 @@ struct expected_move_base : expected_copy_base { }; // This class manages conditionally having a trivial copy assignment operator -template +template < + class T, class E, + bool = is_void_or>::value && + IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value && + IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value && IS_TRIVIALLY_DESTRUCTIBLE(E)::value> struct expected_copy_assign_base : expected_move_base { using expected_move_base::expected_move_base; }; @@ -694,9 +817,10 @@ struct expected_copy_assign_base : expected_move_base { // move assignable #ifndef TL_EXPECTED_GCC49 template ::value - &&std::is_trivially_move_constructible::value - &&std::is_trivially_move_assignable::value + bool = + is_void_or, + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>::value &&std::is_trivially_destructible::value &&std::is_trivially_move_constructible::value &&std::is_trivially_move_assignable::value> @@ -724,7 +848,7 @@ struct expected_move_assign_base operator=(expected_move_assign_base &&rhs) noexcept( std::is_nothrow_move_constructible::value &&std::is_nothrow_move_assignable::value) { - this->assign(std::move(rhs)); + this->assign(std::move(rhs)); return *this; } }; @@ -732,9 +856,9 @@ struct expected_move_assign_base // expected_delete_ctor_base will conditionally delete copy and move // constructors depending on whether T is copy/move constructible template ::value && + bool EnableCopy = (is_copy_constructible_or_void::value && std::is_copy_constructible::value), - bool EnableMove = (std::is_move_constructible::value && + bool EnableMove = (is_move_constructible_or_void::value && std::is_move_constructible::value)> struct expected_delete_ctor_base { expected_delete_ctor_base() = default; @@ -848,7 +972,7 @@ struct default_constructor_tag { // consturctor if T is not default constructible. // This specialization is for when T is default constructible template ::value> + bool Enable = std::is_default_constructible::value || std::is_void::value> struct expected_default_ctor_base { constexpr expected_default_ctor_base() noexcept = default; constexpr expected_default_ctor_base( @@ -916,13 +1040,16 @@ class expected : private detail::expected_move_assign_base, static_assert(!std::is_same>>::value, "T must not be unexpected"); static_assert(!std::is_reference::value, "E must not be a reference"); - static_assert(!std::is_same::value, "T must not be void"); T *valptr() { return std::addressof(this->m_val); } unexpected *errptr() { return std::addressof(this->m_unexpect); } - T &val() { return this->m_val; } + + template ::value>* = nullptr> + U &val() { return this->m_val; } unexpected &err() { return this->m_unexpect; } - const T &val() const { return this->m_val; } + + template ::value>* = nullptr> + const U &val() const { return this->m_val; } const unexpected &err() const { return this->m_unexpect; } using impl_base = detail::expected_move_assign_base; @@ -1387,15 +1514,17 @@ public: template < class U = 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, - detail::enable_if_t::value> * = - nullptr> +// std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr + > expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); @@ -1411,15 +1540,17 @@ public: /// \exclude template < class U = 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, - detail::enable_if_t::value> * = - nullptr> +// std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr + > expected &operator=(U &&v) { if (has_value()) { val() = std::forward(v); @@ -1571,13 +1702,17 @@ public: /// \returns the stored value /// \requires a value is stored /// \group deref - constexpr const T &operator*() const & { return val(); } + template ::value>* = nullptr> + constexpr const U &operator*() const & { return val(); } /// \group deref - TL_EXPECTED_11_CONSTEXPR T &operator*() & { return val(); } + template ::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { return val(); } /// \group deref - constexpr const T &&operator*() const && { return std::move(val()); } + template ::value>* = nullptr> + constexpr const U &&operator*() const && { return std::move(val()); } /// \group deref - TL_EXPECTED_11_CONSTEXPR T &&operator*() && { return std::move(val()); } + template ::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { return std::move(val()); } /// \returns whether or not the optional has a value /// \group has_value @@ -1585,28 +1720,33 @@ public: /// \group has_value constexpr explicit operator bool() const noexcept { return this->m_has_val; } - - /// \returns the contained value if there is one, otherwise throws [bad_expected_access] + /// \returns the contained value if there is one, otherwise throws + /// [bad_expected_access] + /// /// \group value - TL_EXPECTED_GCC49_CONSTEXPR const T &value() const & { + template ::value>* = nullptr> + TL_EXPECTED_GCC49_CONSTEXPR const U &value() const & { if (!has_value()) throw bad_expected_access(err().value()); return val(); } /// \group value - TL_EXPECTED_11_CONSTEXPR T &value() & { + template ::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { if (!has_value()) throw bad_expected_access(err().value()); return val(); } /// \group value - TL_EXPECTED_GCC49_CONSTEXPR const T &&value() const && { + template ::value>* = nullptr> + TL_EXPECTED_GCC49_CONSTEXPR const U &&value() const && { if (!has_value()) throw bad_expected_access(err().value()); return std::move(val()); } /// \group value - TL_EXPECTED_11_CONSTEXPR T &&value() && { + template ::value>* = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { if (!has_value()) throw bad_expected_access(err().value()); return std::move(val()); @@ -1663,10 +1803,10 @@ template ())), detail::enable_if_t::value> * = nullptr> auto map_impl(Exp &&exp, F &&f) { - using result = expected>; + using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); - return result(monostate{}); + return result(); } return result(unexpect, std::forward(exp).error()); @@ -1690,10 +1830,10 @@ template ())), detail::enable_if_t::value> * = nullptr> -auto map_impl(Exp &&exp, F &&f) -> expected> { +auto map_impl(Exp &&exp, F &&f) -> expected> { if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); - return tl::monostate{}; + return {}; } return unexpected>(std::forward(exp).error()); @@ -1720,11 +1860,10 @@ template , monostate>; if (exp.has_value()) { - return result(*std::forward(exp)); + return result(*std::forward(exp)); } - detail::invoke(std::forward(f), - std::forward(exp).error()); + detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); } #else @@ -1732,7 +1871,8 @@ template (), *std::declval())), detail::enable_if_t::value> * = nullptr> -constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected, detail::decay_t> { +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { using result = ret_t>; return exp.has_value() @@ -1748,11 +1888,10 @@ template expected, monostate> { using result = expected, monostate>; if (exp.has_value()) { - return result(*std::forward(exp)); + return result(*std::forward(exp)); } - detail::invoke(std::forward(f), - std::forward(exp).error()); + detail::invoke(std::forward(f), std::forward(exp).error()); return result(unexpect, monostate{}); } #endif From 2d7dfc8b02fd08498d6d9599d2cef75d35ede183 Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Sat, 23 Dec 2017 15:13:38 +0000 Subject: [PATCH 2/5] More void support --- tests/bases.cpp | 7 ++ tests/constructors.cpp | 11 +++ tl/expected.hpp | 176 +++++++++++++++++++++++------------------ 3 files changed, 118 insertions(+), 76 deletions(-) diff --git a/tests/bases.cpp b/tests/bases.cpp index 4f3fc80..55fc3ff 100644 --- a/tests/bases.cpp +++ b/tests/bases.cpp @@ -15,6 +15,13 @@ TEST_CASE("Triviality", "[bases.triviality]") { REQUIRE(std::is_trivially_move_assignable>::value); REQUIRE(std::is_trivially_destructible>::value); + REQUIRE(std::is_trivially_copy_constructible>::value); + REQUIRE(std::is_trivially_copy_assignable>::value); + REQUIRE(std::is_trivially_move_constructible>::value); + REQUIRE(std::is_trivially_move_assignable>::value); + REQUIRE(std::is_trivially_destructible>::value); + + { struct T { T(const T&) = default; diff --git a/tests/constructors.cpp b/tests/constructors.cpp index 986b69b..07f2644 100644 --- a/tests/constructors.cpp +++ b/tests/constructors.cpp @@ -120,4 +120,15 @@ TEST_CASE("Constructors", "[constructors]") { REQUIRE(!std::is_trivially_move_assignable::value); # endif } + + { + tl::expected e; + REQUIRE(e); + } + + { + tl::expected e (tl::unexpect, 42); + REQUIRE(!e); + REQUIRE(e.error() == 42); + } } diff --git a/tl/expected.hpp b/tl/expected.hpp index 68c371e..0d02a06 100644 --- a/tl/expected.hpp +++ b/tl/expected.hpp @@ -19,7 +19,6 @@ #include #include -#include #include #include @@ -266,11 +265,11 @@ using expected_enable_from_other = detail::enable_if_t< template using is_copy_constructible_or_void = is_void_or>; - + template using is_move_constructible_or_void = is_void_or>; - + } // namespace detail /// \exclude @@ -528,10 +527,16 @@ template struct expected_operations_base : expected_storage_base { using expected_storage_base::expected_storage_base; - template void construct(Args &&... args) noexcept { - new (std::addressof(this->m_val)) T(std::forward(args)...); - this->m_has_val = true; - } + template void construct(Args &&... args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template + void construct_with(Rhs && rhs) noexcept { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } template void construct_error(Args &&... args) noexcept { new (std::addressof(this->m_unexpect)) @@ -678,6 +683,12 @@ struct expected_operations_base : expected_storage_base { this->m_has_val = true; } + template + void construct_with(Rhs && rhs) noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&... args) noexcept { new (std::addressof(this->m_unexpect)) unexpected(std::forward(args)...); @@ -700,7 +711,7 @@ struct expected_operations_base : expected_storage_base { } } } - + bool has_value() const { return this->m_has_val; } TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { @@ -716,7 +727,7 @@ struct expected_operations_base : expected_storage_base { } #endif }; - + // This class manages conditionally having a trivial copy constructor // This specialization is for when T and E are trivially copy constructible template : expected_operations_base { expected_copy_base(const expected_copy_base &rhs) : expected_operations_base(no_init) { if (rhs.has_value()) { - this->construct(rhs.get()); + this->construct_with(rhs); } else { this->construct_error(rhs.geterr()); } @@ -772,7 +783,7 @@ struct expected_move_base : expected_copy_base { std::is_nothrow_move_constructible::value) : expected_copy_base(no_init) { if (rhs.has_value()) { - this->construct(std::move(rhs.get())); + this->construct_with(std::move(rhs)); } else { this->construct_error(std::move(rhs.geterr())); } @@ -1048,7 +1059,7 @@ class expected : private detail::expected_move_assign_base, U &val() { return this->m_val; } unexpected &err() { return this->m_unexpect; } - template ::value>* = nullptr> + template ::value>* = nullptr> const U &val() const { return this->m_val; } const unexpected &err() const { return this->m_unexpect; } @@ -1072,46 +1083,26 @@ public: /// is returned. /// \synopsis template \nconstexpr auto and_then(F &&f) &; template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), **this) - : result(unexpect, this->error()); + return and_then_impl(*this, std::forward(f)); } /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) &&; template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), std::move(**this)) - : result(unexpect, std::move(this->error())); + return and_then_impl(std::move(*this), std::forward(f)); } /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) const &; template constexpr auto and_then(F &&f) const & { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), **this) - : result(unexpect, this->error()); + return and_then_impl(*this, std::forward(f)); } #ifndef TL_EXPECTED_NO_CONSTRR /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) const &&; template constexpr auto and_then(F &&f) const && { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), std::move(**this)) - : result(unexpect, std::move(this->error())); + return and_then_impl(std::move(*this), std::forward(f)); } #endif @@ -1126,50 +1117,30 @@ public: /// is returned. /// \synopsis template \nconstexpr auto and_then(F &&f) &; template - TL_EXPECTED_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), **this) - : result(unexpect, this->error()); + 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)); } /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) &&; template - TL_EXPECTED_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), **this) - : result(unexpect, std::move(this->error())); + 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)); } /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) const &; template - constexpr detail::invoke_result_t and_then(F &&f) const & { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), **this) - : result(unexpect, this->error()); + 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 /// \group and_then /// \synopsis template \nconstexpr auto and_then(F &&f) const &&; template - constexpr detail::invoke_result_t and_then(F &&f) const && { - using result = detail::invoke_result_t; - static_assert(detail::is_expected::value, - "F must return an expected"); - - return has_value() ? detail::invoke(std::forward(f), **this) - : result(unexpect, std::move(this->error())); + 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 @@ -1514,15 +1485,16 @@ public: template < class U = T, + class G = T, detail::enable_if_t::value> * = nullptr, - 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_assignable::value && std::is_nothrow_move_constructible::value)> * = nullptr > expected &operator=(U &&v) { @@ -1540,15 +1512,16 @@ public: /// \exclude template < class U = T, + class G = T, detail::enable_if_t::value> * = nullptr, - 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_assignable::value && std::is_nothrow_move_constructible::value)> * = nullptr > expected &operator=(U &&v) { @@ -1705,13 +1678,13 @@ public: template ::value>* = nullptr> constexpr const U &operator*() const & { return val(); } /// \group deref - template ::value>* = nullptr> + template ::value>* = nullptr> TL_EXPECTED_11_CONSTEXPR U &operator*() & { return val(); } /// \group deref - template ::value>* = nullptr> + template ::value>* = nullptr> constexpr const U &&operator*() const && { return std::move(val()); } /// \group deref - template ::value>* = nullptr> + template ::value>* = nullptr> TL_EXPECTED_11_CONSTEXPR U &&operator*() && { return std::move(val()); } /// \returns whether or not the optional has a value @@ -1724,28 +1697,28 @@ public: /// [bad_expected_access] /// /// \group value - template ::value>* = nullptr> + template ::value>* = nullptr> TL_EXPECTED_GCC49_CONSTEXPR const U &value() const & { if (!has_value()) throw bad_expected_access(err().value()); return val(); } /// \group value - template ::value>* = nullptr> + template ::value>* = nullptr> TL_EXPECTED_11_CONSTEXPR U &value() & { if (!has_value()) throw bad_expected_access(err().value()); return val(); } /// \group value - template ::value>* = nullptr> + template ::value>* = nullptr> TL_EXPECTED_GCC49_CONSTEXPR const U &&value() const && { if (!has_value()) throw bad_expected_access(err().value()); return std::move(val()); } /// \group value - template ::value>* = nullptr> + template ::value>* = nullptr> TL_EXPECTED_11_CONSTEXPR U &&value() && { if (!has_value()) throw bad_expected_access(err().value()); @@ -1782,10 +1755,61 @@ public: /// \exclude namespace detail { -template using exp_t = typename detail::decay_t::error_type; +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 (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + 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, exp.error()); +} + +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, + "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, exp.error()); +} +#else + templatestruct 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, exp.error()); +} + +template (), + *std::declval())), + 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, exp.error()); +} +#endif + #ifdef TL_EXPECTED_CXX14 template (), From 9ff078cc240117911201d9d6885fdbd0cea0d593 Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Sat, 23 Dec 2017 15:17:14 +0000 Subject: [PATCH 3/5] Remove bad tests --- tests/bases.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/bases.cpp b/tests/bases.cpp index 55fc3ff..d38d3d3 100644 --- a/tests/bases.cpp +++ b/tests/bases.cpp @@ -16,9 +16,7 @@ TEST_CASE("Triviality", "[bases.triviality]") { REQUIRE(std::is_trivially_destructible>::value); REQUIRE(std::is_trivially_copy_constructible>::value); - REQUIRE(std::is_trivially_copy_assignable>::value); REQUIRE(std::is_trivially_move_constructible>::value); - REQUIRE(std::is_trivially_move_assignable>::value); REQUIRE(std::is_trivially_destructible>::value); From 6ec9ffded904c63c04084179262ec75ceb03c904 Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Sat, 23 Dec 2017 15:30:44 +0000 Subject: [PATCH 4/5] Constexpr fixes --- tl/expected.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tl/expected.hpp b/tl/expected.hpp index 0d02a06..83f2123 100644 --- a/tl/expected.hpp +++ b/tl/expected.hpp @@ -25,6 +25,9 @@ #if (defined(_MSC_VER) && _MSC_VER == 1900) /// \exclude #define TL_EXPECTED_MSVC2015 +#define TL_EXPECTED_MSVC2015_CONSTEXPR +#else +#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr #endif #if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ @@ -458,7 +461,7 @@ template struct expected_storage_base { // `T` is `void`, `E` is trivially-destructible template struct expected_storage_base { - constexpr expected_storage_base() : m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base() : m_has_val(true) {} constexpr expected_storage_base(no_init_t) : m_has_val(false) {} constexpr expected_storage_base(in_place_t) : m_has_val(true) {} From 6a0a4f8e45d0d7dfe4f071db965370f1ef13ace8 Mon Sep 17 00:00:00 2001 From: Simon Brand Date: Sat, 23 Dec 2017 15:44:00 +0000 Subject: [PATCH 5/5] Update version number --- tl/expected.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tl/expected.hpp b/tl/expected.hpp index 7e69795..9ec1a1f 100644 --- a/tl/expected.hpp +++ b/tl/expected.hpp @@ -15,7 +15,7 @@ #define TL_EXPECTED_HPP #define TL_EXPECTED_VERSION_MAJOR 0 -#define TL_EXPECTED_VERSION_MINOR 1 +#define TL_EXPECTED_VERSION_MINOR 2 #include #include