diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d9ebc1..06ca9de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) # Make test executable set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/main.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/extensions.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/tests/assignment.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tests/constructors.cpp) add_executable(tests ${TEST_SOURCES}) diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/COPYING @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/expected.hpp b/expected.hpp index 8252372..338604b 100644 --- a/expected.hpp +++ b/expected.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #if (defined(_MSC_VER) && _MSC_VER == 1900) #define TL_EXPECTED_MSVC2015 @@ -41,7 +42,7 @@ #if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ defined(TL_EXPECTED_GCC49)) && \ - !defined(TL_EXPECTED_GCC50) + !defined(TL_EXPECTED_GCC54) /// \exclude #define TL_EXPECTED_11_CONSTEXPR #else @@ -57,6 +58,13 @@ template using enable_if_t = typename std::enable_if::type; template using decay_t = typename std::decay::type; +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + // Trait for checking if a type is a tl::expected template struct is_expected_impl : std::false_type {}; template @@ -155,6 +163,11 @@ constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { return lhs.value() >= rhs.value(); } +template +unexpected make_unexpected (E&& e) { + return unexpected(std::forward(e)); +} + struct unexpect_t { unexpect_t() = default; }; @@ -385,7 +398,51 @@ template struct expected_storage_base { }; // TODO, conditionally delete things -template class expected_ctor_base {}; + 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; + }; + + 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; + }; + + 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; + }; + + 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; + }; } // namespace detail template class bad_expected_access : public std::exception { @@ -407,7 +464,8 @@ private: template class expected : private detail::expected_storage_base, - private detail::expected_ctor_base { + private detail::expected_assign_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"); @@ -432,7 +490,7 @@ public: typedef E error_type; typedef unexpected unexpected_type; -#ifdef TL_EXPECTED_CXX14 +#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 /// if there is one. \requires `std::invoke(std::forward(f), value())` @@ -546,7 +604,7 @@ public: #endif #endif -#ifdef TL_EXPECTED_CXX14 +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) /// \brief Carries out some operation on the stored object if there is one. /// \returns Let `U` be the result of `std::invoke(std::forward(f), /// value())`. Returns a `std::expected`. The return value is empty if @@ -606,7 +664,7 @@ public: #endif #endif -#ifdef TL_EXPECTED_CXX14 +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) /// \brief Carries out some operation on the stored object if there is one. /// \returns Let `U` be the result of `std::invoke(std::forward(f), /// value())`. Returns a `std::expected`. The return value is empty if @@ -688,14 +746,14 @@ public: detail::enable_if_t::value> * = nullptr> explicit constexpr expected(unexpected const &e) - : storage_base(unexpect, e) {} + : storage_base(unexpect, e.value()) {} template < class G = E, detail::enable_if_t::value> * = nullptr, detail::enable_if_t::value> * = nullptr> - constexpr expected(unexpected const &e) : storage_base(unexpect, e) {} + constexpr expected(unexpected const &e) : storage_base(unexpect, e.value()) {} template < class G = E, @@ -703,7 +761,7 @@ public: detail::enable_if_t::value> * = nullptr> explicit constexpr expected(unexpected &&e) noexcept( std::is_nothrow_constructible::value) - : storage_base(unexpect, std::move(e)) {} + : storage_base(unexpect, std::move(e.value())) {} template < class G = E, @@ -711,7 +769,7 @@ public: detail::enable_if_t::value> * = nullptr> constexpr expected(unexpected &&e) noexcept( std::is_nothrow_constructible::value) - : storage_base(unexpect, std::move(e)) {} + : storage_base(unexpect, std::move(e.value())) {} template ::value> * = @@ -794,15 +852,164 @@ public: std::is_convertible::value> * = nullptr> constexpr expected(U &&v) : expected(in_place, std::forward(v)) {} - // TODO - expected &operator=(const expected &); - expected &operator=(expected &&) noexcept; - template expected &operator=(U &&); - template expected &operator=(const unexpected &); - template expected &operator=(unexpected &&) noexcept; - template void emplace(Args &&...); - template - void emplace(std::initializer_list, Args &&...); + // TODO conditionally delete + expected& operator=(const expected& rhs) { + return assign(rhs); + } + + expected& operator=(expected&& rhs) { + return assign(std::move(rhs)); + } + + template , 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> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } + else { + err().~unexpected(); + ::new (valptr()) T (std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + template , 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> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } + else { + auto tmp = std::move(err()); + err().~unexpected(); + try { + ::new (valptr()) T (std::move(v)); + this->m_has_val = true; + } + catch (...) { + err() = std::move(tmp); + throw; + } + } + + return *this; + } + + + template ::value && std::is_assignable::value>* = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } + else { + val().~T(); + ::new (errptr()) unexpected (rhs); + this->m_has_val = false; + } + + return *this; + } + + template ::value && std::is_move_assignable::value>* = nullptr> + expected &operator=(unexpected && rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } + else { + val().~T(); + ::new (errptr()) unexpected (std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template ::value>* = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } + else { + err().~unexpected(); + ::new (valptr()) T (std::forward(args)...); + this->m_has_val = true; + } + } + + template ::value>* = nullptr> + void emplace(Args &&... args) { + if (has_value()) { + val() = T(std::forward(args)...); + } + else { + auto tmp = std::move(err()); + err().~unexpected(); + + try { + ::new (valptr()) T (std::forward(args)...); + this->m_has_val = true; + } + catch (...) { + err() = std::move(tmp); + throw; + } + } + } + + template &, Args&&...>::value>* = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t (il, std::forward(args)...); + val() = std::move(t); + } + else { + err().~unexpected(); + ::new (valptr()) T (il, std::forward(args)...); + this->m_has_val = true; + } + } + + template &, Args&&...>::value>* = nullptr> + void emplace(std::initializer_list il, Args &&... args) { + if (has_value()) { + T t (il, std::forward(args)...); + val() = std::move(t); + } + else { + auto tmp = std::move(err()); + err().~unexpected(); + + try { + ::new (valptr()) T (il, std::forward(args)...); + this->m_has_val = true; + } + catch (...) { + err() = std::move(tmp); + throw; + } + } + } // TODO SFINAE void swap(expected &rhs) noexcept( @@ -874,7 +1081,109 @@ public: return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); } + 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 { template using err_t = typename detail::decay_t::error_type; template using ret_t = expected>; @@ -883,7 +1192,7 @@ private: class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> - static constexpr auto map_impl(Exp &&exp, F &&f) { + constexpr auto map_impl(Exp &&exp, F &&f) { using result = ret_t; return exp.has_value() ? result(detail::invoke(std::forward(f), *std::forward(exp))) @@ -894,7 +1203,7 @@ private: class Ret = decltype(detail::invoke(std::declval(), *std::declval())), detail::enable_if_t::value> * = nullptr> - static auto map_impl(Exp &&exp, F &&f) { + auto map_impl(Exp &&exp, F &&f) { using result = expected>; if (exp.has_value()) { detail::invoke(std::forward(f), *std::forward(exp)); @@ -909,7 +1218,7 @@ private: *std::declval())), detail::enable_if_t::value> * = nullptr> - static constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t { + constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t { using result = ret_t; return exp.has_value() ? result(detail::invoke(std::forward(f), @@ -922,7 +1231,7 @@ private: *std::declval())), detail::enable_if_t::value> * = nullptr> - static 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{}; @@ -932,11 +1241,11 @@ private: } #endif -#ifdef TL_EXPECTED_CXX14 +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && !defined(TL_EXPECTED_GCC54) template (), *std::declval()))> - static constexpr auto map_error_impl(Exp &&exp, F &&f) { + constexpr auto map_error_impl(Exp &&exp, F &&f) { using result = ret_t; return exp.has_value() ? result(*std::forward(exp)) @@ -948,15 +1257,19 @@ private: template (), *std::declval()))> - static constexpr auto map_error_impl(Exp &&exp, F &&f) -> ret_t { + constexpr auto map_error_impl(Exp &&exp, F &&f) -> ret_t { using result = ret_t; - return exp.has_value() ? result(detail::invoke(std::forward(f), - *std::forward(exp))) - : result(unexpect, std::forward(exp).error()); + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, + detail::invoke(std::forward(f), + std::forward(exp).error())); } #endif -}; + + + } // TODO template class expected {}; diff --git a/tests/assignment.cpp b/tests/assignment.cpp new file mode 100644 index 0000000..ac0514f --- /dev/null +++ b/tests/assignment.cpp @@ -0,0 +1,76 @@ +#include "catch.hpp" +#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); + + 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 = 42; + REQUIRE(e1); + REQUIRE(*e1 == 42); + + 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 = e3; + REQUIRE(e1); + REQUIRE(*e1 == 21); + + 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); + +} + +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; + }; + + 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; +} diff --git a/tests/emplace.cpp b/tests/emplace.cpp new file mode 100644 index 0000000..6169541 --- /dev/null +++ b/tests/emplace.cpp @@ -0,0 +1,4 @@ +#include "catch.hpp" +#include "expected.hpp" + +TEST_CASE("Emplace", "[emplace]") {