diff --git a/.gitignore b/.gitignore index a343c3ed..c37e0208 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .idea/ .idea_modules/ .build*/ +.scratch*/ dist*/ result* .cache/ diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 00000000..0a4455d2 --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,20 @@ +# SonarCloud Automatic Analysis configuration. +# https://docs.sonarsource.com/sonarqube-cloud/analyzing-source-code/automatic-analysis +# +# Automatic Analysis (the GitHub App, no CI workflow) reads only this file and +# only a subset of properties from it; sonar-project.properties is ignored. +# --- Sources / tests --- +sonar.sources=include,examples +sonar.tests=tests +sonar.sourceEncoding=UTF-8 + +# --- Path exclusions --- +# Local CMake build directories and FetchContent-pulled deps are out of scope. +sonar.exclusions=.build*/**,**/_deps/** + +# --- Duplicated code (CPD) --- +# The test suite intentionally repeats assertion shapes parameterised over +# value categories, constexpr/runtime evaluation, and the noexcept profile +# (potentially-throwing vs nothrow) of the user's special member functions, +# so don't count them toward the duplications metric. +sonar.cpd.exclusions=tests/** diff --git a/include/pfn/expected.hpp b/include/pfn/expected.hpp index 43cba350..3754d8a4 100644 --- a/include/pfn/expected.hpp +++ b/include/pfn/expected.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,13 @@ #define ASSERT(...) assert((__VA_ARGS__) == true) #endif +// This header is a polyfill for std::expected, tracking the C++ working draft +// and changes planned for the future standard revisions. As a deliberate extension, +// member functions whose `noexcept` specification is left unspecified by the +// standard are given a `noexcept` clause derived from the properties of the +// underlying types T, E and (where applicable) invocable arguments. Each such +// clause is marked inline with a "// extension" trailing comment. + namespace pfn { template class bad_expected_access; @@ -89,6 +97,12 @@ constexpr bool _is_valid_unexpected = // && not _is_some_unexpected // && not ::std::is_const_v // && not ::std::is_volatile_v; + +// Helper used as noexcept(...) operand where we want to evaluate both: +// * noexcept of an expression itself (e.g. operator==) AND +// * noexcept of the expression's implicit conversion to bool +// May only be used in unevaluated contexts; any ODR-use will trigger a link error. +constexpr bool _implicit_to_bool(bool) noexcept; } // namespace detail template class unexpected { @@ -137,9 +151,11 @@ template class unexpected { swap(e_, other.e_); } - template constexpr friend bool operator==(unexpected const &x, unexpected const &y) + template + constexpr friend bool operator==(unexpected const &x, unexpected const &y) // + noexcept(noexcept(detail::_implicit_to_bool(x.error() == y.error()))) // extension { - return x.e_ == y.e_; + return x.error() == y.error(); } constexpr friend void swap(unexpected &x, unexpected &y) noexcept(noexcept(x.swap(y))) @@ -216,7 +232,7 @@ template class expected { not ::std::is_same_v> // && not detail::_is_some_unexpected<::std::remove_cvref_t> // && ::std::is_constructible_v // - && ::std::is_assignable_v // + && ::std::is_assignable_v // && (::std::is_nothrow_constructible_v // || ::std::is_nothrow_move_constructible_v // || ::std::is_nothrow_move_constructible_v)>; @@ -233,6 +249,39 @@ template class expected { New tmp(::std::forward(args)...); ::std::destroy_at(::std::addressof(oldval)); ::std::construct_at(::std::addressof(newval), std::move(tmp)); + } else if constexpr (::std::is_trivially_copyable_v) { + // Workaround for https://github.com/llvm/llvm-project/issues/196520: + // clang on aarch64 sinks the snapshot load past the store-through-newval + // when Old's TBAA tag differs from New's, corrupting the strong-EG + // restoration on catch. A byte-buffer snapshot via std::memcpy is opaque + // to TBAA, and preserving *oldval in place across the try (no destroy_at) + // makes the catch a plain byte restore -- which for trivially-copyable + // (and therefore trivially-destructible) Old is observationally identical + // to the destroy-and-recreate branch below. + // (Old is also trivially-destructible -- implied by is_trivially_copyable_v + // per [class.prop]/1 -- so skipping destroy_at is observationally identical + // to the destroy-and-recreate branch below.) + if (not ::std::is_constant_evaluated()) { + alignas(Old) unsigned char _bytes[sizeof(Old)]; + ::std::memcpy(_bytes, ::std::addressof(oldval), sizeof(Old)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::memcpy(::std::addressof(oldval), _bytes, sizeof(Old)); + throw; + } + } else { + // LCOV_EXCL_START constant-evaluated only; runtime branches are above and below + Old tmp(std::move(oldval)); + ::std::destroy_at(::std::addressof(oldval)); + try { + ::std::construct_at(::std::addressof(newval), ::std::forward(args)...); + } catch (...) { + ::std::construct_at(::std::addressof(oldval), std::move(tmp)); + throw; + } + // LCOV_EXCL_STOP + } } else { Old tmp(std::move(oldval)); ::std::destroy_at(::std::addressof(oldval)); @@ -575,9 +624,8 @@ template class expected { } template - constexpr expected &operator=(U &&s) // - noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v - && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) // extension + constexpr expected &operator=(U &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension requires(_can_convert_assign::value) { if (set_) { @@ -591,8 +639,8 @@ template class expected { template constexpr expected &operator=(unexpected const &s) // - noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v - && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) // extension + noexcept(::std::is_nothrow_assignable_v + && ::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v && ::std::is_assignable_v && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) @@ -607,9 +655,8 @@ template class expected { } template - constexpr expected &operator=(unexpected &&s) // - noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v - && (::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) // extension + constexpr expected &operator=(unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v && ::std::is_assignable_v && (::std::is_nothrow_constructible_v || ::std::is_nothrow_move_constructible_v || ::std::is_nothrow_move_constructible_v)) @@ -699,7 +746,7 @@ template class expected { constexpr T &&operator*() && noexcept { return ::std::move(*(this->operator->())); } constexpr explicit operator bool() const noexcept { return set_; } constexpr bool has_value() const noexcept { return set_; } - constexpr bool has_error() const noexcept { return !set_; } + constexpr bool has_error() const noexcept { return !set_; } // P3798 constexpr T const &value() const & { static_assert(::std::is_copy_constructible_v); @@ -912,7 +959,7 @@ template class expected { template requires(not ::std::is_void_v) constexpr friend bool operator==(expected const &x, expected const &y) // - noexcept(noexcept(static_cast(*x == *y)) && noexcept(static_cast(x.error() == y.error()))) // extension + noexcept(noexcept(detail::_implicit_to_bool(*x == *y)) && noexcept(detail::_implicit_to_bool(x.error() == y.error()))) // extension requires ( // requires { { *x == *y } -> std::convertible_to; @@ -923,30 +970,36 @@ requires { { if (x.has_value() != y.has_value()) { return false; - } else if (x.has_value()) { + } + if (x.has_value()) { return *x == *y; - } else { - return x.error() == y.error(); } + return x.error() == y.error(); } template requires(not detail::_is_some_expected) constexpr friend bool operator==(expected const &x, const T2 &v) // - noexcept(noexcept(static_cast(*x == v))) // extension + noexcept(noexcept(detail::_implicit_to_bool(*x == v))) // extension requires requires { { *x == v } -> std::convertible_to; } { - return x.has_value() && static_cast(*x == v); + if (!x.has_value()) { + return false; + } + return *x == v; } template constexpr friend bool operator==(expected const &x, unexpected const &e) // - noexcept(noexcept(static_cast(x.error() == e.error()))) // extension + noexcept(noexcept(detail::_implicit_to_bool(x.error() == e.error()))) // extension requires requires { { x.error() == e.error() } -> std::convertible_to; } { - return (not x.has_value()) && static_cast(x.error() == e.error()); + if (x.has_value()) { + return false; + } + return x.error() == e.error(); } private: @@ -974,16 +1027,6 @@ class expected { template using _can_move_convert = _can_convert_detail; template friend class expected; - template - using _can_convert = ::std::bool_constant< // - not ::std::is_same_v<::std::remove_cvref_t, ::std::in_place_t> // - && not ::std::is_same_v<::std::remove_cvref_t, unexpect_t> // LWG4222 - && not ::std::is_same_v> // - && not detail::_is_some_unexpected<::std::remove_cvref_t> // - && ::std::is_constructible_v // - && (not ::std::is_same_v> // - || not detail::_is_some_expected<::std::remove_cvref_t>)>; - template static constexpr auto _and_then(Self &&self, Fn &&fn) // noexcept(::std::is_nothrow_invocable_v && ::std::is_nothrow_constructible_v) @@ -1203,7 +1246,7 @@ class expected { template constexpr expected &operator=(unexpected const &s) // - noexcept(::std::is_nothrow_assignable_v + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v && ::std::is_assignable_v) { @@ -1218,8 +1261,8 @@ class expected { } template - constexpr expected &operator=(unexpected &&s) // - noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension + constexpr expected &operator=(unexpected &&s) // + noexcept(::std::is_nothrow_assignable_v && ::std::is_nothrow_constructible_v) // extension requires(::std::is_constructible_v && ::std::is_assignable_v) { if (set_) { @@ -1276,7 +1319,7 @@ class expected { // [expected.void.obs], observers constexpr explicit operator bool() const noexcept { return set_; } constexpr bool has_value() const noexcept { return set_; } - constexpr bool has_error() const noexcept { return !set_; } + constexpr bool has_error() const noexcept { return !set_; } // P3798 constexpr void operator*() const noexcept { ASSERT(set_); } constexpr void value() const & { @@ -1456,27 +1499,30 @@ class expected { template requires(::std::is_void_v) constexpr friend bool operator==(expected const &x, expected const &y) // - noexcept(noexcept(static_cast((x.error() == y.error())))) // extension + noexcept(noexcept(detail::_implicit_to_bool((x.error() == y.error())))) // extension requires requires { { x.error() == y.error() } -> std::convertible_to; } { if (x.has_value() != y.has_value()) { return false; - } else if (x.has_value()) { + } + if (x.has_value()) { return true; - } else { - return x.error() == y.error(); } + return x.error() == y.error(); } template constexpr friend bool operator==(expected const &x, unexpected const &e) // - noexcept(noexcept(static_cast(x.error() == e.error()))) // extension + noexcept(noexcept(detail::_implicit_to_bool(x.error() == e.error()))) // extension requires requires { { x.error() == e.error() } -> std::convertible_to; } { - return (not x.has_value()) && static_cast(x.error() == e.error()); + if (x.has_value()) { + return false; + } + return x.error() == e.error(); } private: diff --git a/tests/fn/choice.cpp b/tests/fn/choice.cpp index 3a6d1959..61e821fa 100644 --- a/tests/fn/choice.cpp +++ b/tests/fn/choice.cpp @@ -162,7 +162,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") constexpr auto b = fn(helper{{0.5, 2.0}, 19}); static_assert(std::is_same_v); static_assert(b.has_value()); - static_assert(b.value().get_ptr()->v == 19 * helper::from_rval); + static_assert(b.value().get_ptr()->v == 19 * from_rval); } WHEN("move from rvalue") @@ -172,7 +172,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") auto const a = fn(helper{9}); static_assert(std::is_same_v); CHECK(a.has_value()); - CHECK(a.value().get_ptr()->v == 9 * helper::from_rval); + CHECK(a.value().get_ptr()->v == 9 * from_rval); auto b = fn(true); static_assert(std::is_same_v); @@ -192,7 +192,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") constexpr auto b = fn(helper{{0.5, 2.0}, 17}); static_assert(std::is_same_v); static_assert(b.has_value()); - static_assert(b.value().get_ptr()->v == 17 * helper::from_rval_const); + static_assert(b.value().get_ptr()->v == 17 * from_rval_const); } WHEN("move from const rvalue") @@ -202,7 +202,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") auto const a = fn(helper{7}); static_assert(std::is_same_v); CHECK(a.has_value()); - CHECK(a.value().get_ptr()->v == 7 * helper::from_rval_const); + CHECK(a.value().get_ptr()->v == 7 * from_rval_const); auto b = fn(true); static_assert(std::is_same_v); @@ -222,7 +222,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") constexpr auto b = fn(helper{{0.5, 2.0}, 17}); static_assert(std::is_same_v); static_assert(b.has_value()); - static_assert(b.value().get_ptr()->v == 17 * helper::from_lval); + static_assert(b.value().get_ptr()->v == 17 * from_lval); } WHEN("copy from lvalue") @@ -237,7 +237,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") auto b = fn(helper{13}); static_assert(std::is_same_v); CHECK(b.has_value()); - CHECK(b.value().get_ptr()->v == 13 * helper::from_lval); + CHECK(b.value().get_ptr()->v == 13 * from_lval); } WHEN("constexpr copy from const lvalue") @@ -252,7 +252,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") constexpr auto b = fn(helper{{0.5, 2.0}, 15}); static_assert(std::is_same_v); static_assert(b.has_value()); - static_assert(b.value().get_ptr()->v == 15 * helper::from_lval_const); + static_assert(b.value().get_ptr()->v == 15 * from_lval_const); } WHEN("copy from const lvalue") @@ -267,7 +267,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") auto b = fn(helper{5}); static_assert(std::is_same_v); CHECK(b.has_value()); - CHECK(b.value().get_ptr()->v == 5 * helper::from_lval_const); + CHECK(b.value().get_ptr()->v == 5 * from_lval_const); } WHEN("copy ctor") @@ -278,7 +278,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") auto const b = std::as_const(a); CHECK(b.has_value()); - CHECK(b.value().get_ptr()->v == 23 * helper::from_lval_const); + CHECK(b.value().get_ptr()->v == 23 * from_lval_const); } WHEN("move ctor") @@ -289,7 +289,7 @@ TEST_CASE("choice non-monadic functionality", "[choice]") auto const b = std::move(a); CHECK(b.has_value()); - CHECK(b.value().get_ptr()->v == 29 * helper::from_rval); + CHECK(b.value().get_ptr()->v == 29 * from_rval); } } @@ -301,14 +301,14 @@ TEST_CASE("choice non-monadic functionality", "[choice]") fn::sum h{helper{1}}; h.get_ptr()->v = 17; T const a{std::move(h)}; - CHECK(a.value().get_ptr()->v == 17 * helper::from_rval); + CHECK(a.value().get_ptr()->v == 17 * from_rval); } WHEN("copy from const lvalue") { fn::sum h{helper{1}}; h.get_ptr()->v = 19; T const a{std::as_const(h)}; - CHECK(a.value().get_ptr()->v == 19 * helper::from_lval_const); + CHECK(a.value().get_ptr()->v == 19 * from_lval_const); } } diff --git a/tests/pfn/expected.cpp b/tests/pfn/expected.cpp index 3fcdbb16..4a677512 100644 --- a/tests/pfn/expected.cpp +++ b/tests/pfn/expected.cpp @@ -3,6 +3,7 @@ // Distributed under the ISC License. See accompanying file LICENSE.md // or copy at https://opensource.org/licenses/ISC +#include "catch2/catch_test_macros.hpp" #ifndef PFN_TEST_VALIDATION // TODO: Add death tests. Until then, empty definition to avoid false "no coverage" reports #define LIBFN_ASSERT(...) @@ -106,28 +107,28 @@ TEST_CASE("bad_expected_access", "[expected][polyfill][bad_expected_access]") { b.error().v = 11; T c = b; - CHECK(c.error().v == 11 * helper::from_lval_const); + CHECK(c.error().v == 11 * from_lval_const); } SECTION("lval const") { b.error().v = 13; T c = std::as_const(b); - CHECK(c.error().v == 13 * helper::from_lval_const); + CHECK(c.error().v == 13 * from_lval_const); } SECTION("rval") { b.error().v = 17; T c = std::move(b); - CHECK(c.error().v == 17 * helper::from_rval); + CHECK(c.error().v == 17 * from_rval); } SECTION("rval cont") { b.error().v = 19; T c = std::move(std::as_const(b)); - CHECK(c.error().v == 19 * helper::from_lval_const); + CHECK(c.error().v == 19 * from_lval_const); } } @@ -140,28 +141,28 @@ TEST_CASE("bad_expected_access", "[expected][polyfill][bad_expected_access]") { b.error().v = 11; a = b; - CHECK(a.error().v == 11 * helper::from_lval_const); + CHECK(a.error().v == 11 * from_lval_const); } SECTION("lval const") { b.error().v = 13; a = std::as_const(b); - CHECK(a.error().v == 13 * helper::from_lval_const); + CHECK(a.error().v == 13 * from_lval_const); } SECTION("rval") { b.error().v = 17; a = std::move(b); - CHECK(a.error().v == 17 * helper::from_rval); + CHECK(a.error().v == 17 * from_rval); } SECTION("rval const") { b.error().v = 19; a = std::move(std::as_const(b)); - CHECK(a.error().v == 19 * helper::from_lval_const); + CHECK(a.error().v == 19 * from_lval_const); } } @@ -174,28 +175,28 @@ TEST_CASE("bad_expected_access", "[expected][polyfill][bad_expected_access]") { b.error().v = 11; c = b.error(); - CHECK(c.v == 11 * helper::from_lval); + CHECK(c.v == 11 * from_lval); } SECTION("lval const") { b.error().v = 13; c = std::as_const(b).error(); - CHECK(c.v == 13 * helper::from_lval_const); + CHECK(c.v == 13 * from_lval_const); } SECTION("rval") { b.error().v = 17; c = std::move(b).error(); - CHECK(c.v == 17 * helper::from_rval); + CHECK(c.v == 17 * from_rval); } SECTION("rval const") { b.error().v = 19; c = std::move(std::as_const(b)).error(); - CHECK(c.v == 19 * helper::from_rval_const); + CHECK(c.v == 19 * from_rval_const); } } @@ -270,7 +271,7 @@ TEST_CASE("unexpected", "[expected][polyfill][unexpected]") { unexpected c{helper{2}}; - CHECK(c.error().v == 2 * helper::from_rval); + CHECK(c.error().v == 2 * from_rval); CHECK(c != unexpected{helper(3)}); static_assert(std::is_same_v>); static_assert(std::is_nothrow_constructible_v); @@ -298,7 +299,7 @@ TEST_CASE("unexpected", "[expected][polyfill][unexpected]") CHECK(c.error().v == 3 * 5); static_assert(not std::is_nothrow_constructible_v); - c.error().v *= helper::from_rval; + c.error().v *= from_rval; CHECK(c == unexpected(std::in_place, helper{15})); } @@ -309,14 +310,14 @@ TEST_CASE("unexpected", "[expected][polyfill][unexpected]") unexpected c(std::in_place, {3.0, 5.0}); auto const d = 3 * 5; CHECK(c.error().v == d); - static_assert(std::is_nothrow_constructible_v); + static_assert(std::is_nothrow_constructible_v); } SECTION("no forwarded args") { unexpected c(std::in_place, {2.0, 2.5}); CHECK(c.error().v == 5); - static_assert(std::is_nothrow_constructible_v); + static_assert(std::is_nothrow_constructible_v); } SECTION("exception thrown") @@ -341,28 +342,28 @@ TEST_CASE("unexpected", "[expected][polyfill][unexpected]") { unexpected t{13}; a = t.error(); - CHECK(a.v == 13 * helper::from_lval); + CHECK(a.v == 13 * from_lval); } SECTION("lval const") { unexpected const t{17}; a = t.error(); - CHECK(a.v == 17 * helper::from_lval_const); + CHECK(a.v == 17 * from_lval_const); } SECTION("rval") { unexpected t{19}; a = std::move(t).error(); - CHECK(a.v == 19 * helper::from_rval); + CHECK(a.v == 19 * from_rval); } SECTION("rval const") { unexpected const t{23}; a = std::move(t).error(); - CHECK(a.v == 23 * helper::from_rval_const); + CHECK(a.v == 23 * from_rval_const); } } @@ -374,28 +375,28 @@ TEST_CASE("unexpected", "[expected][polyfill][unexpected]") { unexpected t{13}; a = t; - CHECK(a.error().v == 13 * helper::from_lval_const); + CHECK(a.error().v == 13 * from_lval_const); } SECTION("lval const") { unexpected const t{17}; a = t; - CHECK(a.error().v == 17 * helper::from_lval_const); + CHECK(a.error().v == 17 * from_lval_const); } SECTION("rval") { unexpected t{19}; a = std::move(t); - CHECK(a.error().v == 19 * helper::from_rval); + CHECK(a.error().v == 19 * from_rval); } SECTION("rval const") { unexpected const t{23}; a = std::move(t); - CHECK(a.error().v == 23 * helper::from_lval_const); + CHECK(a.error().v == 23 * from_lval_const); } } @@ -406,12 +407,69 @@ TEST_CASE("unexpected", "[expected][polyfill][unexpected]") unexpected b{helper{1}}; b.error().v = 3; a.swap(b); - CHECK(a.error().v == 3 * helper::swapped); - CHECK(b.error().v == 2 * helper::swapped); + CHECK(a.error().v == 3 * swapped); + CHECK(b.error().v == 2 * swapped); b.error() = helper{11}; swap(a, b); - CHECK(a.error().v == 11 * helper::from_rval * helper::swapped); - CHECK(b.error().v == 3 * helper::swapped * helper::swapped); + CHECK(a.error().v == 11 * from_rval * swapped); + CHECK(b.error().v == 3 * swapped * swapped); + } + + SECTION("equality") + { +#ifndef PFN_TEST_VALIDATION + constexpr bool extension = true; +#else + constexpr bool extension = false; +#endif + + static_assert(unexpected{1} == unexpected{1}); + static_assert(unexpected{1} != unexpected{2}); + +#if !defined(PFN_TEST_VALIDATION) || !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 200000 + // libc++ < 20 rejects heterogeneous comparisons in std::unexpected; fixed in 20 + static_assert(unexpected{1} == unexpected{1L}); // heterogeneous + static_assert(unexpected{1} != unexpected{2L}); // heterogeneous + static_assert(unexpected{1L} == unexpected{1}); // heterogeneous, swapped + static_assert(unexpected{0} == unexpected{false}); // heterogeneous, narrowing op== +#endif + + unexpected const a{3}; + unexpected const b{3}; + unexpected const c{4}; + CHECK(a == b); + CHECK(a != c); + CHECK_FALSE(a == c); + CHECK_FALSE(a != b); + +#if !defined(PFN_TEST_VALIDATION) || !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 200000 + unexpected const d{3}; + unexpected const e{3L}; + unexpected const f{4L}; + CHECK(d == e); + CHECK(d != f); + CHECK_FALSE(d == f); + CHECK_FALSE(d != e); +#endif + + static_assert(noexcept(std::declval() == std::declval())); // prerequisite + static_assert(not extension + || noexcept(std::declval const &>() == std::declval const &>())); + static_assert(not extension + || noexcept(std::declval const &>() == std::declval const &>())); + static_assert( + not extension + || noexcept(std::declval const &>() == std::declval const &>())); + + struct E { + int v; + constexpr bool operator==(E const &o) const noexcept(false) { return v == o.v; } + constexpr bool operator==(int o) const noexcept(false) { return v == o; } + }; + static_assert(not noexcept(std::declval() == std::declval())); // prerequisite + static_assert(not noexcept(std::declval() == std::declval())); // prerequisite + static_assert(not noexcept(std::declval const &>() == std::declval const &>())); + static_assert(not noexcept(std::declval const &>() == std::declval const &>())); } SECTION("constexpr") @@ -454,6 +512,11 @@ template struct nothrow_swappable { template concept is_swappable = requires { swap(std::declval(), std::declval()); }; +template +concept is_nothrow_swappable = requires { + { swap(std::declval(), std::declval()) } noexcept; +}; + } // namespace TEST_CASE("expected non void", "[expected][polyfill]") @@ -584,9 +647,9 @@ TEST_CASE("expected non void", "[expected][polyfill]") { using T = expected; static_assert(std::is_constructible_v>); - static_assert(std::is_constructible_v>); + static_assert(std::is_constructible_v>); static_assert(not std::is_nothrow_constructible_v>); - static_assert(not extension || std::is_nothrow_constructible_v>); + static_assert(not extension || std::is_nothrow_constructible_v>); constexpr T b(expected(unexpect, Error::unknown)); static_assert(b.error() == Error::unknown); @@ -601,7 +664,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(std::is_constructible_v>); T d(expected(unexpect, 2)); - CHECK(d.error().v == 2 * helper::from_rval); + CHECK(d.error().v == 2 * from_rval); } SECTION("value from other expected lval const") @@ -609,7 +672,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") using T = expected; static_assert(std::is_constructible_v const &>); static_assert(not std::is_nothrow_constructible_v const &>); - static_assert(not extension || std::is_nothrow_constructible_v const &>); + static_assert(not extension || std::is_nothrow_constructible_v const &>); constexpr expected v(5); constexpr expected e(unexpect, Error::file_not_found); @@ -629,7 +692,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") expected const e(unexpect, 3); T d(e); - CHECK(d.error().v == 3 * helper::from_lval_const); + CHECK(d.error().v == 3 * from_lval_const); } SECTION("converting") @@ -639,14 +702,14 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_constructible_v); static_assert(std::is_constructible_v); static_assert(not extension || std::is_nothrow_constructible_v); - static_assert(std::is_constructible_v); - static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); T const b(11); CHECK(b.value().v == 11); T const c(helper(13)); - CHECK(c.value().v == 13 * helper::from_rval); + CHECK(c.value().v == 13 * from_rval); } SECTION("from unexpected rval") @@ -654,8 +717,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") using T = expected; static_assert(std::is_constructible_v>); static_assert(not std::is_nothrow_constructible_v>); - static_assert(std::is_constructible_v>); - static_assert(not extension || std::is_nothrow_constructible_v>); + static_assert(std::is_constructible_v>); + static_assert(not extension || std::is_nothrow_constructible_v>); constexpr expected a(unexpected(true)); static_assert(a.error() == 1); @@ -682,8 +745,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_constructible_v); static_assert(std::is_constructible_v); static_assert(not extension || std::is_nothrow_constructible_v); - static_assert(std::is_constructible_v); - static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); T const b(std::in_place, 11, 13); CHECK(b.value().v == 11 * 13); @@ -706,8 +769,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_constructible_v); static_assert(std::is_constructible_v); static_assert(not extension || std::is_nothrow_constructible_v); - static_assert(std::is_constructible_v); - static_assert(not extension || std::is_nothrow_constructible_v); + static_assert(std::is_constructible_v); + static_assert(not extension || std::is_nothrow_constructible_v); T const b(unexpect, 11, 13); CHECK(b.error().v == 11 * 13); @@ -795,28 +858,28 @@ TEST_CASE("expected non void", "[expected][polyfill]") #ifndef PFN_TEST_VALIDATION CHECK(not b.has_error()); #endif - CHECK(b.value().v == 13 * helper::from_lval_const); + CHECK(b.value().v == 13 * from_lval_const); T c = std::as_const(a); CHECK(c.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(not c.has_error()); #endif - CHECK(c.value().v == 13 * helper::from_lval_const); + CHECK(c.value().v == 13 * from_lval_const); T d = std::move(std::as_const(a)); // no overload for lval const CHECK(d.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(not d.has_error()); #endif - CHECK(d.value().v == 13 * helper::from_lval_const); + CHECK(d.value().v == 13 * from_lval_const); T e = std::move(a); CHECK(e.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(not e.has_error()); #endif - CHECK(e.value().v == 13 * helper::from_rval); + CHECK(e.value().v == 13 * from_rval); } } @@ -839,28 +902,28 @@ TEST_CASE("expected non void", "[expected][polyfill]") #ifndef PFN_TEST_VALIDATION CHECK(b.has_error()); #endif - CHECK(b.error().v == 33 * helper::from_lval_const); + CHECK(b.error().v == 33 * from_lval_const); T c = std::as_const(a); CHECK(not c.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(c.has_error()); #endif - CHECK(c.error().v == 33 * helper::from_lval_const); + CHECK(c.error().v == 33 * from_lval_const); T d = std::move(std::as_const(a)); // no overload for lval const CHECK(not d.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(d.has_error()); #endif - CHECK(d.error().v == 33 * helper::from_lval_const); + CHECK(d.error().v == 33 * from_lval_const); T e = std::move(a); CHECK(not e.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(e.has_error()); #endif - CHECK(e.error().v == 33 * helper::from_rval); + CHECK(e.error().v == 33 * from_rval); } } @@ -883,7 +946,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") #ifndef PFN_TEST_VALIDATION CHECK(not b.has_error()); #endif - CHECK(b.value().v == 41 * helper::from_lval_const); + CHECK(b.value().v == 41 * from_lval_const); } { @@ -893,7 +956,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") #ifndef PFN_TEST_VALIDATION CHECK(b.has_error()); #endif - CHECK(b.error().v == 43 * helper::from_lval_const); + CHECK(b.error().v == 43 * from_lval_const); } } @@ -1041,15 +1104,23 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("assignment") { - using M = helper_t<2>; // nothrow move constructible - using E = helper_t<3>; // may throw on move and copy - using C = helper_t<4>; // nothrow copy constructible + using M = helper_t<2>; // nothrow move constructible + using E = helper_t<3>; // may throw on move and copy + using C = helper_t<4>; // nothrow copy constructible + using G = helper_t<33>; // nothrow copy constructible; throwing move constructible + using H = helper_t<40>; // nothrow copy/move constructible; throwing copy/move assignable static_assert(not std::is_nothrow_copy_constructible_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_copy_assignable_v); + static_assert(not std::is_nothrow_move_assignable_v); SECTION("from rval") { @@ -1062,7 +1133,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); a = T(std::in_place, 5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1070,7 +1141,41 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); a = helper(5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); + } + +#ifndef _MSC_VER + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + T a(std::in_place, 3); + a = 5; + CHECK(a.value().v == 5 * from_rval); + } +#endif + + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + T a(std::in_place, 3); + a = 5; + CHECK(a.value() == 5); + } + + { // the rvalue conversion-assignment operator propagates a throwing T::operator= + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + T a(std::in_place, 7); + a = H(11); + CHECK(a.value().v == 11 * from_rval); + + try { + a = H({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value().v == 11 * from_rval); + } } } @@ -1084,7 +1189,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T a(std::in_place, 3); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -1101,7 +1206,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not extension || std::is_nothrow_assignable_v &&>); T a(std::in_place, 4); a = unexpected(5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -1123,7 +1228,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T a(std::in_place, 3); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -1141,7 +1246,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_assignable_v &&>); T a(std::in_place, 4); a = unexpected(5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -1154,6 +1259,29 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK(a.value() == 4); } } + + { +#ifndef _MSC_VER + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(std::in_place, 0); + a = unexpected(11); + CHECK(a.error().v == 11); +#endif + + { + using T = expected; + static_assert(not std::is_nothrow_assignable_v &&>); + T a(std::in_place, 7); + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value() == 7); + } + } + } } SECTION("nothrow copy") @@ -1164,7 +1292,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T a(std::in_place, 3); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -1182,7 +1310,15 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_assignable_v &&>); T a(std::in_place, 4); a = unexpected(5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); + } + + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(std::in_place, 0); + a = unexpected(11); + CHECK(a.error() == 11); } { @@ -1208,7 +1344,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T a(unexpect, Error::file_not_found); a = T(std::in_place, 5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1226,7 +1362,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not extension || std::is_nothrow_assignable_v); T a(unexpect, Error::file_not_found); a = M(5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1248,7 +1384,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T a(unexpect, Error::file_not_found); a = T(std::in_place, 5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1266,7 +1402,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_assignable_v); T a(unexpect, Error::file_not_found); a = E(5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1279,6 +1415,29 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK(a.error() == Error::file_not_found); } } + + { +#ifndef _MSC_VER + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + T a(unexpect, 0); + a = 5; + CHECK(a.value().v == 5); +#endif + + { // the rvalue conversion-assignment operator propagates the underlying throw + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + try { + a = G({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + } } SECTION("nothrow copy") @@ -1289,7 +1448,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T a(unexpect, Error::file_not_found); a = T(std::in_place, 5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1307,7 +1466,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_assignable_v); T a(unexpect, Error::file_not_found); a = C(5); - CHECK(a.value().v == 5 * helper::from_rval); + CHECK(a.value().v == 5 * from_rval); } { @@ -1330,10 +1489,36 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, 3); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); a = unexpected(7); - CHECK(a.error().v == 7 * helper::from_rval); + CHECK(a.error().v == 7 * from_rval); + +#ifndef _MSC_VER + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(unexpect, 0); + a = unexpected(11); + CHECK(a.error().v == 11 * from_rval); + } +#endif + + { // the rvalue conversion-assignment operator propagates a throwing E::operator= + using T = expected; + static_assert(not std::is_nothrow_assignable_v &&>); + T a(unexpect, 7); + a = unexpected(11); + CHECK(a.error().v == 11 * from_rval); + + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error().v == 11 * from_rval); + } + } } SECTION("constexpr") @@ -1351,8 +1536,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr T a = fn(T(unexpect, Error::file_not_found)); static_assert(a.error() == Error::file_not_found); - constexpr T b = fn(T(std::in_place, helper::list_t(), 7)); - static_assert(b.value().v == 7 * helper::from_rval * helper::from_rval); + constexpr T b = fn(T(std::in_place, helper_list_t(), 7)); + static_assert(b.value().v == 7 * from_rval * from_rval); SUCCEED(); } @@ -1361,7 +1546,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { { constexpr auto fn = [](T &&v) constexpr -> T { - T tmp{std::in_place, helper::list_t(), 13}; + T tmp{std::in_place, helper_list_t(), 13}; tmp = std::move(v); return tmp; }; @@ -1369,8 +1554,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr T a = fn(T(unexpect, Error::file_not_found)); static_assert(a.error() == Error::file_not_found); - constexpr T b = fn(T(std::in_place, helper::list_t(), 11)); - static_assert(b.value().v == 11 * helper::from_rval * helper::from_rval); + constexpr T b = fn(T(std::in_place, helper_list_t(), 11)); + static_assert(b.value().v == 11 * from_rval * from_rval); SUCCEED(); } @@ -1389,15 +1574,15 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); T const b(std::in_place, 5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const); + CHECK(a.value().v == 5 * from_lval_const); T c(std::in_place, 7); a = c; - CHECK(a.value().v == 7 * helper::from_lval_const); + CHECK(a.value().v == 7 * from_lval_const); T const d(std::in_place, 11); a = std::move(d); - CHECK(a.value().v == 11 * helper::from_lval_const); + CHECK(a.value().v == 11 * from_lval_const); } { @@ -1406,15 +1591,44 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); helper const b(5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const); + CHECK(a.value().v == 5 * from_lval_const); helper c(7); a = c; - CHECK(a.value().v == 7 * helper::from_lval); + CHECK(a.value().v == 7 * from_lval); helper const d(11); a = std::move(d); - CHECK(a.value().v == 11 * helper::from_rval_const); + CHECK(a.value().v == 11 * from_rval_const); + } + +#ifndef _MSC_VER + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + T a(std::in_place, 3); + int const i = 5; + a = i; + CHECK(a.value().v == 5 * from_rval); + } +#endif + + { // the const-lvalue conversion-assignment operator propagates a throwing T::operator= + using T = expected; + static_assert(not std::is_nothrow_assignable_v); + T a(std::in_place, 7); + H const b(11); + a = b; + CHECK(a.value().v == 11 * from_lval_const); + + try { + H const c({0.0}); + a = c; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value().v == 11 * from_lval_const); + } } } @@ -1429,7 +1643,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const * helper::from_rval); + CHECK(a.error().v == 5 * from_lval_const * from_rval); } { @@ -1449,7 +1663,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 4); unexpected const b(5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const * helper::from_rval); + CHECK(a.error().v == 5 * from_lval_const * from_rval); } { @@ -1474,7 +1688,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -1494,7 +1708,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 4); unexpected const b(5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -1508,6 +1722,31 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK(a.value() == 4); } } + + { +#ifndef _MSC_VER + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(std::in_place, 0); + unexpected const b(7); + a = b; + CHECK(a.error().v == 7); +#endif + + { + using T = expected; // M used for throwing copy constructor + static_assert(not std::is_nothrow_assignable_v const &>); + T a(std::in_place, 7); + try { + unexpected const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.value().v == 7); + } + } + } } SECTION("nothrow copy") @@ -1519,7 +1758,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 3); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -1538,7 +1777,16 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(std::in_place, 4); unexpected const b(5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); + } + + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(std::in_place, 0); + unexpected const b(11); + a = b; + CHECK(a.error() == 11); } { @@ -1565,7 +1813,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, Error::file_not_found); T const b(std::in_place, 5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + CHECK(a.value().v == 5 * from_lval_const * from_rval); } { @@ -1585,7 +1833,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, Error::file_not_found); M const b(5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + CHECK(a.value().v == 5 * from_lval_const * from_rval); } { @@ -1610,7 +1858,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, Error::file_not_found); T const b(std::in_place, 5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const); + CHECK(a.value().v == 5 * from_lval_const); } { @@ -1630,7 +1878,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, Error::file_not_found); E const b(5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const); + CHECK(a.value().v == 5 * from_lval_const); } { @@ -1644,6 +1892,31 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK(a.error() == Error::file_not_found); } } + + { +#ifndef _MSC_VER + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v); + T a(unexpect, 0); + int const i = 5; + a = i; + CHECK(a.value().v == 5); +#endif + + { + using T = expected; // M used for throwing copy constructor + static_assert(not std::is_nothrow_assignable_v); + T a(unexpect, Error::file_not_found); + try { + M const b({0.0}); + a = b; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error() == Error::file_not_found); + } + } + } } SECTION("nothrow copy") @@ -1655,7 +1928,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, Error::file_not_found); T const b(std::in_place, 5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const); + CHECK(a.value().v == 5 * from_lval_const); } { @@ -1674,7 +1947,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, Error::file_not_found); C const b(5); a = b; - CHECK(a.value().v == 5 * helper::from_lval_const); + CHECK(a.value().v == 5 * from_lval_const); } { @@ -1698,43 +1971,109 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, 3); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); unexpected const c(7); a = c; - CHECK(a.error().v == 7 * helper::from_lval_const); + CHECK(a.error().v == 7 * from_lval_const); + +#ifndef _MSC_VER + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(unexpect, 0); + unexpected const u(7); + a = u; + CHECK(a.error().v == 7 * from_rval); + } +#endif + + { // the const-lvalue conversion-assignment operator propagates a throwing E::operator= + using T = expected; + static_assert(not std::is_nothrow_assignable_v const &>); + T a(unexpect, 7); + unexpected const b(11); + a = b; + CHECK(a.error().v == 11 * from_lval_const); + + try { + unexpected const c({0.0}); + a = c; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error().v == 11 * from_lval_const); + } + } } } SECTION("constexpr") { - using T = expected; - constexpr T c{unexpect, Error::file_not_found}; - constexpr T d{std::in_place, helper::list_t(), 5}; - - SECTION("from error") + SECTION("nothrow move") { - constexpr auto fn = [](T const &v) constexpr -> T { - T tmp{unexpect, Error::unknown}; - tmp = v; - return tmp; - }; + using T = expected; + static_assert(std::is_nothrow_move_constructible_v); - constexpr T a = fn(c); - static_assert(a.error() == Error::file_not_found); + constexpr T c{unexpect, Error::file_not_found}; + constexpr T d{std::in_place, helper_list_t(), 5}; - constexpr T b = fn(d); - static_assert(b.value().v == 5 * helper::from_lval_const * helper::from_rval); + SECTION("from error") + { + constexpr auto fn = [](auto &&v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp = std::forward(v); + return tmp; + }; - SUCCEED(); + constexpr T a = fn(c); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(d); + static_assert(b.value().v == 5 * from_lval_const * from_rval); + + constexpr T e = fn(T{std::in_place, helper_list_t(), 7}); + static_assert(e.value().v == 7 * from_rval * from_rval); + + SUCCEED(); + } + + SECTION("from value") + { + constexpr auto fn = [](auto &&v) constexpr -> T { + T tmp{std::in_place, helper_list_t(), 13}; + tmp = std::forward(v); + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.error() == Error::file_not_found); + + constexpr T b = fn(d); + static_assert(b.value().v == 5 * from_lval_const * from_rval); + + constexpr T e = fn(T{std::in_place, helper_list_t(), 7}); + static_assert(e.value().v == 7 * from_rval * from_rval); + + SUCCEED(); + } } - SECTION("from value") + SECTION("no nothrow move") { + // Not actually throwing here (that wouldn't be constexpr) but use type E + // which potentially could throw when move-constructed inside assignment. + static_assert(not std::is_nothrow_move_constructible_v); + + SECTION("from error") { - constexpr auto fn = [](T const &v) constexpr -> T { - T tmp{std::in_place, helper::list_t(), 13}; - tmp = v; + using T = expected; // error->value: Old=Error, New=E + constexpr T c{unexpect, Error::file_not_found}; + constexpr T d{std::in_place, helper_list_t(), 5}; + + constexpr auto fn = [](auto &&v) constexpr -> T { + T tmp{unexpect, Error::unknown}; + tmp = std::forward(v); return tmp; }; @@ -1742,7 +2081,34 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(a.error() == Error::file_not_found); constexpr T b = fn(d); - static_assert(b.value().v == 5 * helper::from_lval_const * helper::from_rval); + static_assert(b.value().v == 5 * from_lval_const * from_rval); + + constexpr T e = fn(T{std::in_place, helper_list_t(), 7}); + static_assert(e.value().v == 7 * from_rval * from_rval); + + SUCCEED(); + } + + SECTION("from value") + { + using T = expected; // value->error: Old=int, New=E + constexpr T c{unexpect, helper_list_t(), 12}; + constexpr T d{std::in_place, 42}; + + constexpr auto fn = [](auto &&v) constexpr -> T { + T tmp{std::in_place, 13}; + tmp = std::forward(v); + return tmp; + }; + + constexpr T a = fn(c); + static_assert(a.error().v == 12 * from_lval_const * from_rval); + + constexpr T b = fn(d); + static_assert(b.value() == 42); + + constexpr T e = fn(T{unexpect, helper_list_t(), 3}); + static_assert(e.error().v == 3 * from_rval * from_rval); SUCCEED(); } @@ -1786,7 +2152,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 5}; + constexpr helper c{helper_list_t(), 5}; SECTION("from error") { @@ -1797,10 +2163,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") }; constexpr T a = fn(c); - static_assert(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + static_assert(a.value().v == 5 * from_lval_const * from_rval); - constexpr T b = fn(helper::list_t{3.0, 11.0}, 7); - static_assert(b.value().v == 3 * 11 * 7 * helper::from_rval); + constexpr T b = fn(helper_list_t{3.0, 11.0}, 7); + static_assert(b.value().v == 3 * 11 * 7 * from_rval); SUCCEED(); } @@ -1809,16 +2175,16 @@ TEST_CASE("expected non void", "[expected][polyfill]") { { constexpr auto fn = [](auto &&...args) constexpr -> T { - T tmp{std::in_place, helper::list_t(), 13}; + T tmp{std::in_place, helper_list_t(), 13}; tmp.emplace(std::forward(args)...); return tmp; }; constexpr T a = fn(c); - static_assert(a.value().v == 5 * helper::from_lval_const * helper::from_rval); + static_assert(a.value().v == 5 * from_lval_const * from_rval); - constexpr T b = fn(helper::list_t{3.0, 11.0}, 7); - static_assert(b.value().v == 3 * 11 * 7 * helper::from_rval); + constexpr T b = fn(helper_list_t{3.0, 11.0}, 7); + static_assert(b.value().v == 3 * 11 * 7 * from_rval); SUCCEED(); } @@ -1886,13 +2252,15 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(not is_swappable>); SUCCEED(); } - SECTION("nothrow-swappable non-nothrow-move-constructible") + SECTION("nothrow-swappable, no-nothrow-move-constructible") { struct A : nothrow_swappable { A(A &&) noexcept(false) {} @@ -1911,13 +2279,15 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(not is_swappable>); SUCCEED(); } - SECTION("swappabla, non-nothrow-move-constructible") + SECTION("swappable, non-nothrow-move-constructible") { struct A : swappable { A(A &&) noexcept(false) {} @@ -1936,23 +2306,25 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); { using T = expected; static_assert(is_swappable); - static_assert(not noexcept(swap(std::declval(), std::declval()))); + static_assert(not is_nothrow_swappable); } { using T = expected; static_assert(is_swappable); - static_assert(not noexcept(swap(std::declval(), std::declval()))); + static_assert(not is_nothrow_swappable); } SUCCEED(); } - SECTION("nothrow-swappabla, non-nothrow-move-constructible") + SECTION("nothrow-swappable, mixed-nothrow-move-constructible") { struct A : nothrow_swappable { A(A &&) noexcept(false) {} @@ -1971,23 +2343,25 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); { using T = expected; static_assert(is_swappable); - static_assert(not noexcept(swap(std::declval(), std::declval()))); + static_assert(not is_nothrow_swappable); } { using T = expected; static_assert(is_swappable); - static_assert(not noexcept(swap(std::declval(), std::declval()))); + static_assert(not is_nothrow_swappable); } SUCCEED(); } - SECTION("nothrow-swappabla, nothrow-move-constructible") + SECTION("nothrow-swappable, nothrow-move-constructible") { struct A : nothrow_swappable { A(A &&) noexcept(true) = default; @@ -2006,17 +2380,56 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(is_nothrow_swappable>); + static_assert(is_swappable>); + static_assert(is_nothrow_swappable>); + + { + using T = expected; + static_assert(is_swappable); + static_assert(is_nothrow_swappable); + } + { + using T = expected; + static_assert(is_swappable); + static_assert(is_nothrow_swappable); + } + + SUCCEED(); + } + + SECTION("swappable, nothrow-move-constructible") + { + struct A : swappable { + A(A &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + struct B : swappable { + B(B &&) noexcept(true) = default; + }; + static_assert(std::is_swappable_v); + static_assert(not std::is_nothrow_swappable_v); + static_assert(std::is_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + + static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); { using T = expected; static_assert(is_swappable); - static_assert(noexcept(swap(std::declval(), std::declval()))); + static_assert(not is_nothrow_swappable); } { using T = expected; static_assert(is_swappable); - static_assert(noexcept(swap(std::declval(), std::declval()))); + static_assert(not is_nothrow_swappable); } SUCCEED(); @@ -2030,8 +2443,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(7); T b(13); swap(a, b); - CHECK(a.value().v == 13 * helper::swapped); - CHECK(b.value().v == 7 * helper::swapped); + CHECK(a.value().v == 13 * swapped); + CHECK(b.value().v == 7 * swapped); } SECTION("error") @@ -2040,8 +2453,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, 17); T b(unexpect, 23); swap(a, b); - CHECK(a.error().v == 23 * helper::swapped); - CHECK(b.error().v == 17 * helper::swapped); + CHECK(a.error().v == 23 * swapped); + CHECK(b.error().v == 17 * swapped); } } @@ -2051,8 +2464,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(unexpect, 19); T b(27); swap(a, b); - CHECK(a.value().v == 27 * helper::from_rval); - CHECK(b.error().v == 19 * helper::from_rval * helper::from_rval); + CHECK(a.value().v == 27 * from_rval); + CHECK(b.error().v == 19 * from_rval * from_rval); } SECTION("swap value/error") @@ -2063,8 +2476,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(17); T b(unexpect, 29); swap(a, b); - CHECK(a.error().v == 29 * helper::from_rval * helper::from_rval); - CHECK(b.value().v == 17 * helper::from_rval); + CHECK(a.error().v == 29 * from_rval * from_rval); + CHECK(b.value().v == 17 * from_rval); } static_assert(not std::is_nothrow_move_constructible_v>); @@ -2077,8 +2490,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(13); T b(unexpect, 23); swap(a, b); - CHECK(a.error().v == 23 * helper::from_rval * helper::from_rval); - CHECK(b.value().v == 13 * helper::from_rval); + CHECK(a.error().v == 23 * from_rval * from_rval); + CHECK(b.value().v == 13 * from_rval); } SECTION("exception") @@ -2090,7 +2503,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") FAIL(); } catch (std::runtime_error const &) { CHECK(a.value().v == 0); - CHECK(b.error().v == 23 * helper::from_rval * helper::from_rval); + CHECK(b.error().v == 23 * from_rval * from_rval); } } } @@ -2103,8 +2516,8 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a(7); T b(unexpect, 11); swap(a, b); - CHECK(a.error().v == 11 * helper::from_rval); - CHECK(b.value().v == 7 * helper::from_rval * helper::from_rval); + CHECK(a.error().v == 11 * from_rval); + CHECK(b.value().v == 7 * from_rval * from_rval); } SECTION("exception") @@ -2115,7 +2528,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") swap(a, b); FAIL(); } catch (std::runtime_error const &) { - CHECK(a.value().v == 13 * helper::from_rval * helper::from_rval); + CHECK(a.value().v == 13 * from_rval * from_rval); CHECK(b.error().v == 0); } } @@ -2180,20 +2593,20 @@ TEST_CASE("expected non void", "[expected][polyfill]") T a = {13}; CHECK(a); helper b{1}; - CHECK((b = a.value()).v == 13 * helper::from_lval); - CHECK((b = std::as_const(a).value()).v == 13 * helper::from_lval_const); - CHECK((b = std::move(std::as_const(a)).value()).v == 13 * helper::from_rval_const); - CHECK((b = std::move(a).value()).v == 13 * helper::from_rval); + CHECK((b = a.value()).v == 13 * from_lval); + CHECK((b = std::as_const(a).value()).v == 13 * from_lval_const); + CHECK((b = std::move(std::as_const(a)).value()).v == 13 * from_rval_const); + CHECK((b = std::move(a).value()).v == 13 * from_rval); } { T a = {17}; helper b{1}; CHECK(a); - CHECK((b = *a).v == 17 * helper::from_lval); - CHECK((b = *std::as_const(a)).v == 17 * helper::from_lval_const); - CHECK((b = *std::move(std::as_const(a))).v == 17 * helper::from_rval_const); - CHECK((b = *std::move(a)).v == 17 * helper::from_rval); + CHECK((b = *a).v == 17 * from_lval); + CHECK((b = *std::as_const(a)).v == 17 * from_lval_const); + CHECK((b = *std::move(std::as_const(a))).v == 17 * from_rval_const); + CHECK((b = *std::move(a)).v == 17 * from_rval); } { @@ -2242,10 +2655,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") { helper b{1}; - CHECK((b = a.error()).v == 17 * helper::from_lval); - CHECK((b = std::as_const(a).error()).v == 17 * helper::from_lval_const); - CHECK((b = std::move(std::as_const(a)).error()).v == 17 * helper::from_rval_const); - CHECK((b = std::move(a).error()).v == 17 * helper::from_rval); + CHECK((b = a.error()).v == 17 * from_lval); + CHECK((b = std::as_const(a).error()).v == 17 * from_lval_const); + CHECK((b = std::move(std::as_const(a)).error()).v == 17 * from_rval_const); + CHECK((b = std::move(a).error()).v == 17 * from_rval); } } @@ -2253,18 +2666,18 @@ TEST_CASE("expected non void", "[expected][polyfill]") { using T = expected; static_assert(not noexcept(std::declval().value_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); static_assert(not noexcept(std::declval().value_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); #ifndef _MSC_VER SECTION("value") { T a(7); - CHECK(a.value_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::as_const(a).value_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::move(std::as_const(a)).value_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::move(a).value_or(0) == helper(7 * helper::from_rval)); + CHECK(a.value_or(0) == helper(7 * from_lval_const)); + CHECK(std::as_const(a).value_or(0) == helper(7 * from_lval_const)); + CHECK(std::move(std::as_const(a)).value_or(0) == helper(7 * from_lval_const)); + CHECK(std::move(a).value_or(0) == helper(7 * from_rval)); } SECTION("error") @@ -2278,10 +2691,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T const a(unexpect, Error::unknown); helper b(11); - CHECK(a.value_or(b) == helper(11 * helper::from_lval)); - CHECK(a.value_or(std::as_const(b)) == helper(11 * helper::from_lval_const)); - CHECK(a.value_or(std::move(std::as_const(b))) == helper(11 * helper::from_rval_const)); - CHECK(a.value_or(std::move(b)) == helper(11 * helper::from_rval)); + CHECK(a.value_or(b) == helper(11 * from_lval)); + CHECK(a.value_or(std::as_const(b)) == helper(11 * from_lval_const)); + CHECK(a.value_or(std::move(std::as_const(b))) == helper(11 * from_rval_const)); + CHECK(a.value_or(std::move(b)) == helper(11 * from_rval)); } } #endif @@ -2293,12 +2706,12 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_copy_constructible_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_convertible_v); - static_assert(std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); static_assert(not noexcept(std::declval().value_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); static_assert(not noexcept(std::declval().value_or(std::declval()))); - static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); } { @@ -2306,12 +2719,12 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_convertible_v); - static_assert(std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); static_assert(not noexcept(std::declval().value_or(std::declval()))); - static_assert(not noexcept(std::declval().value_or(std::declval()))); + static_assert(not noexcept(std::declval().value_or(std::declval()))); static_assert(not noexcept(std::declval().value_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().value_or(std::declval()))); } SUCCEED(); @@ -2321,17 +2734,17 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; SECTION("lval const") { { constexpr T a(std::in_place, {3.0}, 5); - static_assert(a.value_or(c).v == 3 * 5 * helper::from_lval_const); + static_assert(a.value_or(c).v == 3 * 5 * from_lval_const); } { constexpr T a(unexpect, Error::unknown); - static_assert(a.value_or(c).v == 7 * helper::from_lval_const); + static_assert(a.value_or(c).v == 7 * from_lval_const); } SUCCEED(); @@ -2339,10 +2752,9 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("rval") { - static_assert(T{std::in_place, {3.0}, 5}.value_or(c).v == 3 * 5 * helper::from_rval); - static_assert(T{unexpect, Error::unknown}.value_or(c).v == 7 * helper::from_lval_const); - static_assert(T{unexpect, Error::unknown}.value_or(helper(helper::list_t{7.0}, 3)).v - == 7 * 3 * helper::from_rval); + static_assert(T{std::in_place, {3.0}, 5}.value_or(c).v == 3 * 5 * from_rval); + static_assert(T{unexpect, Error::unknown}.value_or(c).v == 7 * from_lval_const); + static_assert(T{unexpect, Error::unknown}.value_or(helper(helper_list_t{7.0}, 3)).v == 7 * 3 * from_rval); SUCCEED(); } @@ -2353,21 +2765,21 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("error_or") { using T = expected; - SECTION("noexcept extension") + SECTION("noexcept") { static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); } SECTION("error") { T a(unexpect, 7); - CHECK(a.error_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::as_const(a).error_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::move(std::as_const(a)).error_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::move(a).error_or(0) == helper(7 * helper::from_rval)); + CHECK(a.error_or(0) == helper(7 * from_lval_const)); + CHECK(std::as_const(a).error_or(0) == helper(7 * from_lval_const)); + CHECK(std::move(std::as_const(a)).error_or(0) == helper(7 * from_lval_const)); + CHECK(std::move(a).error_or(0) == helper(7 * from_rval)); } SECTION("value") @@ -2381,10 +2793,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") { T const a(23); helper b(11); - CHECK(a.error_or(b) == helper(11 * helper::from_lval)); - CHECK(a.error_or(std::as_const(b)) == helper(11 * helper::from_lval_const)); - CHECK(a.error_or(std::move(std::as_const(b))) == helper(11 * helper::from_rval_const)); - CHECK(a.error_or(std::move(b)) == helper(11 * helper::from_rval)); + CHECK(a.error_or(b) == helper(11 * from_lval)); + CHECK(a.error_or(std::as_const(b)) == helper(11 * from_lval_const)); + CHECK(a.error_or(std::move(std::as_const(b))) == helper(11 * from_rval_const)); + CHECK(a.error_or(std::move(b)) == helper(11 * from_rval)); } } @@ -2395,12 +2807,12 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(not std::is_nothrow_copy_constructible_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_convertible_v); - static_assert(std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); } { @@ -2408,12 +2820,12 @@ TEST_CASE("expected non void", "[expected][polyfill]") static_assert(std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_convertible_v); - static_assert(std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); } SUCCEED(); @@ -2422,18 +2834,18 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; SECTION("lval const") { { constexpr T a(unexpect, {3.0}, 5); - static_assert(a.error_or(c).v == 3 * 5 * helper::from_lval_const); + static_assert(a.error_or(c).v == 3 * 5 * from_lval_const); } { constexpr T a(std::in_place, 13); - static_assert(a.error_or(c).v == 7 * helper::from_lval_const); + static_assert(a.error_or(c).v == 7 * from_lval_const); } SUCCEED(); @@ -2441,9 +2853,9 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("rval") { - static_assert(T{unexpect, {3.0}, 5}.error_or(c).v == 3 * 5 * helper::from_rval); - static_assert(T{std::in_place, 13}.error_or(c).v == 7 * helper::from_lval_const); - static_assert(T{std::in_place, 13}.error_or(helper(helper::list_t{7.0}, 3)).v == 7 * 3 * helper::from_rval); + static_assert(T{unexpect, {3.0}, 5}.error_or(c).v == 3 * 5 * from_rval); + static_assert(T{std::in_place, 13}.error_or(c).v == 7 * from_lval_const); + static_assert(T{std::in_place, 13}.error_or(helper(helper_list_t{7.0}, 3)).v == 7 * 3 * from_rval); SUCCEED(); } @@ -2462,10 +2874,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") = [](auto &&a) constexpr -> expected { return helper(std::forward(a)).v * 2; }; T a(7); - CHECK(a.and_then(fn).value() == 7 * 2 * helper::from_lval); - CHECK(std::as_const(a).and_then(fn).value() == 7 * 2 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).and_then(fn).value() == 7 * 2 * helper::from_rval_const); - CHECK(std::move(a).and_then(fn).value() == 7 * 2 * helper::from_rval); + CHECK(a.and_then(fn).value() == 7 * 2 * from_lval); + CHECK(std::as_const(a).and_then(fn).value() == 7 * 2 * from_lval_const); + CHECK(std::move(std::as_const(a)).and_then(fn).value() == 7 * 2 * from_rval_const); + CHECK(std::move(a).and_then(fn).value() == 7 * 2 * from_rval); } SECTION("error") @@ -2474,16 +2886,16 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr auto fn = [](auto &&) constexpr -> expected { return {0}; }; T a(unexpect, 11); - CHECK(a.and_then(fn).error().v == 11 * helper::from_lval); - CHECK(std::as_const(a).and_then(fn).error().v == 11 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).and_then(fn).error().v == 11 * helper::from_rval_const); - CHECK(std::move(a).and_then(fn).error().v == 11 * helper::from_rval); + CHECK(a.and_then(fn).error().v == 11 * from_lval); + CHECK(std::as_const(a).and_then(fn).error().v == 11 * from_lval_const); + CHECK(std::move(std::as_const(a)).and_then(fn).error().v == 11 * from_rval_const); + CHECK(std::move(a).and_then(fn).error().v == 11 * from_rval); } SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; constexpr auto fn = [](auto &&a) constexpr -> expected { return helper(std::forward(a)).v * 3; }; @@ -2491,7 +2903,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { { constexpr T a(std::in_place, {3.0}, 5); - static_assert(a.and_then(fn).value() == 3 * 3 * 5 * helper::from_lval_const); + static_assert(a.and_then(fn).value() == 3 * 3 * 5 * from_lval_const); } { @@ -2504,7 +2916,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("rval") { - static_assert(T{std::in_place, {3.0}, 5}.and_then(fn) == 3 * 3 * 5 * helper::from_rval); + static_assert(T{std::in_place, {3.0}, 5}.and_then(fn) == 3 * 3 * 5 * from_rval); static_assert(T{unexpect, Error::file_not_found}.and_then(fn).error() == Error::file_not_found); SUCCEED(); @@ -2531,10 +2943,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") }; T a(unexpect, 5); - CHECK(a.or_else(fn).error() == 5 * 3 * helper::from_lval); - CHECK(std::as_const(a).or_else(fn).error() == 5 * 3 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).or_else(fn).error() == 5 * 3 * helper::from_rval_const); - CHECK(std::move(a).or_else(fn).error() == 5 * 3 * helper::from_rval); + CHECK(a.or_else(fn).error() == 5 * 3 * from_lval); + CHECK(std::as_const(a).or_else(fn).error() == 5 * 3 * from_lval_const); + CHECK(std::move(std::as_const(a)).or_else(fn).error() == 5 * 3 * from_rval_const); + CHECK(std::move(a).or_else(fn).error() == 5 * 3 * from_rval); } SECTION("value") @@ -2543,16 +2955,16 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr auto fn = [](auto &&) constexpr -> expected { return {0}; }; T a(13); - CHECK(a.or_else(fn).value().v == 13 * helper::from_lval); - CHECK(std::as_const(a).or_else(fn).value().v == 13 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).or_else(fn).value().v == 13 * helper::from_rval_const); - CHECK(std::move(a).or_else(fn).value().v == 13 * helper::from_rval); + CHECK(a.or_else(fn).value().v == 13 * from_lval); + CHECK(std::as_const(a).or_else(fn).value().v == 13 * from_lval_const); + CHECK(std::move(std::as_const(a)).or_else(fn).value().v == 13 * from_rval_const); + CHECK(std::move(a).or_else(fn).value().v == 13 * from_rval); } SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; constexpr auto fn = [](auto &&a) constexpr -> expected { return helper(std::forward(a)).v * 3; }; @@ -2565,7 +2977,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { constexpr T a(unexpect, {3.0}, 5); - static_assert(a.or_else(fn).value() == 3 * 3 * 5 * helper::from_lval_const); + static_assert(a.or_else(fn).value() == 3 * 3 * 5 * from_lval_const); } SUCCEED(); @@ -2573,7 +2985,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("rval") { - static_assert(T{unexpect, {3.0}, 5}.or_else(fn).value() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{unexpect, {3.0}, 5}.or_else(fn).value() == 3 * 3 * 5 * from_rval); static_assert(T{std::in_place, 13}.or_else(fn).value() == 13); SUCCEED(); @@ -2597,10 +3009,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 2; }; T a(7); - CHECK(a.transform(fn).value() == 7 * 2 * helper::from_lval); - CHECK(std::as_const(a).transform(fn).value() == 7 * 2 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).transform(fn).value() == 7 * 2 * helper::from_rval_const); - CHECK(std::move(a).transform(fn).value() == 7 * 2 * helper::from_rval); + CHECK(a.transform(fn).value() == 7 * 2 * from_lval); + CHECK(std::as_const(a).transform(fn).value() == 7 * 2 * from_lval_const); + CHECK(std::move(std::as_const(a)).transform(fn).value() == 7 * 2 * from_rval_const); + CHECK(std::move(a).transform(fn).value() == 7 * 2 * from_rval); CHECK(a.transform([](auto &&) {}).has_value()); } @@ -2610,24 +3022,24 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr auto fn = [](auto &&) constexpr -> int { return 0; }; T a(unexpect, 11); - CHECK(a.transform(fn).error().v == 11 * helper::from_lval); - CHECK(std::as_const(a).transform(fn).error().v == 11 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).transform(fn).error().v == 11 * helper::from_rval_const); - CHECK(std::move(a).transform(fn).error().v == 11 * helper::from_rval); - CHECK(a.transform([](auto &&) {}).error().v == 11 * helper::from_lval); + CHECK(a.transform(fn).error().v == 11 * from_lval); + CHECK(std::as_const(a).transform(fn).error().v == 11 * from_lval_const); + CHECK(std::move(std::as_const(a)).transform(fn).error().v == 11 * from_rval_const); + CHECK(std::move(a).transform(fn).error().v == 11 * from_rval); + CHECK(a.transform([](auto &&) {}).error().v == 11 * from_lval); } SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; SECTION("lval const") { { constexpr T a(std::in_place, {3.0}, 5); - static_assert(a.transform(fn).value() == 3 * 3 * 5 * helper::from_lval_const); + static_assert(a.transform(fn).value() == 3 * 3 * 5 * from_lval_const); } { @@ -2640,7 +3052,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("rval") { - static_assert(T{std::in_place, {3.0}, 5}.transform(fn) == 3 * 3 * 5 * helper::from_rval); + static_assert(T{std::in_place, {3.0}, 5}.transform(fn) == 3 * 3 * 5 * from_rval); static_assert(T{unexpect, Error::file_not_found}.transform(fn).error() == Error::file_not_found); SUCCEED(); @@ -2664,10 +3076,10 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; T a(unexpect, 5); - CHECK(a.transform_error(fn).error() == 5 * 3 * helper::from_lval); - CHECK(std::as_const(a).transform_error(fn).error() == 5 * 3 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).transform_error(fn).error() == 5 * 3 * helper::from_rval_const); - CHECK(std::move(a).transform_error(fn).error() == 5 * 3 * helper::from_rval); + CHECK(a.transform_error(fn).error() == 5 * 3 * from_lval); + CHECK(std::as_const(a).transform_error(fn).error() == 5 * 3 * from_lval_const); + CHECK(std::move(std::as_const(a)).transform_error(fn).error() == 5 * 3 * from_rval_const); + CHECK(std::move(a).transform_error(fn).error() == 5 * 3 * from_rval); } SECTION("value") @@ -2676,16 +3088,16 @@ TEST_CASE("expected non void", "[expected][polyfill]") constexpr auto fn = [](auto &&) constexpr -> int { return 0; }; T a(13); - CHECK(a.transform_error(fn).value().v == 13 * helper::from_lval); - CHECK(std::as_const(a).transform_error(fn).value().v == 13 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).transform_error(fn).value().v == 13 * helper::from_rval_const); - CHECK(std::move(a).transform_error(fn).value().v == 13 * helper::from_rval); + CHECK(a.transform_error(fn).value().v == 13 * from_lval); + CHECK(std::as_const(a).transform_error(fn).value().v == 13 * from_lval_const); + CHECK(std::move(std::as_const(a)).transform_error(fn).value().v == 13 * from_rval_const); + CHECK(std::move(a).transform_error(fn).value().v == 13 * from_rval); } SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; SECTION("lval const") @@ -2697,7 +3109,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") { constexpr T a(unexpect, {3.0}, 5); - static_assert(a.transform_error(fn).error() == 3 * 3 * 5 * helper::from_lval_const); + static_assert(a.transform_error(fn).error() == 3 * 3 * 5 * from_lval_const); } SUCCEED(); @@ -2705,7 +3117,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") SECTION("rval") { - static_assert(T{unexpect, {3.0}, 5}.transform_error(fn).error() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{unexpect, {3.0}, 5}.transform_error(fn).error() == 3 * 3 * 5 * from_rval); static_assert(T{std::in_place, 13}.transform_error(fn).value() == 13); SUCCEED(); @@ -2737,7 +3149,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK((t1 != u1)); constexpr T t2{unexpect, 12}; - constexpr U u2{std::in_place, helper::list_t(), 13}; + constexpr U u2{std::in_place, helper_list_t(), 13}; static_assert(not(t2 == u2)); static_assert(t2 != u2); } @@ -2813,7 +3225,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK((t1 == v1)); constexpr T t2{std::in_place, {3.0}, 4}; - constexpr helper u2{helper::list_t(), 3, 2, 2}; + constexpr helper u2{helper_list_t(), 3, 2, 2}; static_assert(t2 == u2); static_assert(not(t2 != u2)); CHECK((t1 == t2)); @@ -2830,6 +3242,20 @@ TEST_CASE("expected non void", "[expected][polyfill]") SUCCEED(); }; + SECTION("error state") + { + using T = expected; + T const t1{unexpect, 12}; + helper const u1{12.0}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + + constexpr T t2{unexpect, 12}; + constexpr helper u2{helper_list_t(), 3, 2, 2}; + static_assert(not(t2 == u2)); + static_assert(t2 != u2); + } + SUCCEED(); } @@ -2847,7 +3273,7 @@ TEST_CASE("expected non void", "[expected][polyfill]") CHECK((t1 != u1)); constexpr T t2{unexpect, {3.0}, 4}; - constexpr U u2{std::in_place, helper::list_t(), 3, 2, 2}; + constexpr U u2{std::in_place, helper_list_t(), 3, 2, 2}; static_assert(t2 == u2); static_assert(not(t2 != u2)); CHECK((t1 == t2)); @@ -2863,6 +3289,21 @@ TEST_CASE("expected non void", "[expected][polyfill]") SUCCEED(); } + + SECTION("value state") + { + using T = expected; + using U = unexpected; + T const t1{std::in_place, 12}; + U const u1{std::in_place, {12.0}}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + + constexpr T t2{std::in_place, 12}; + constexpr U u2{std::in_place, helper_list_t(), 3, 2, 2}; + static_assert(not(t2 == u2)); + static_assert(t2 != u2); + } } } } @@ -2962,8 +3403,8 @@ TEST_CASE("expected void", "[expected_void][polyfill]") using T = expected; static_assert(std::is_constructible_v>); static_assert(not std::is_nothrow_constructible_v>); - static_assert(std::is_constructible_v>); - static_assert(not extension || std::is_nothrow_constructible_v>); + static_assert(std::is_constructible_v>); + static_assert(not extension || std::is_nothrow_constructible_v>); constexpr expected a(unexpected(true)); static_assert(a.error() == 1); @@ -3053,28 +3494,28 @@ TEST_CASE("expected void", "[expected_void][polyfill]") #ifndef PFN_TEST_VALIDATION CHECK(b.has_error()); #endif - CHECK(b.error().v == 33 * helper::from_lval_const); + CHECK(b.error().v == 33 * from_lval_const); T c = std::as_const(a); CHECK(not c.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(c.has_error()); #endif - CHECK(c.error().v == 33 * helper::from_lval_const); + CHECK(c.error().v == 33 * from_lval_const); T d = std::move(std::as_const(a)); // no overload for lval const CHECK(not d.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(d.has_error()); #endif - CHECK(d.error().v == 33 * helper::from_lval_const); + CHECK(d.error().v == 33 * from_lval_const); T e = std::move(a); CHECK(not e.has_value()); #ifndef PFN_TEST_VALIDATION CHECK(e.has_error()); #endif - CHECK(e.error().v == 33 * helper::from_rval); + CHECK(e.error().v == 33 * from_rval); } } @@ -3160,15 +3601,23 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("assignment") { - using M = helper_t<2>; // nothrow move constructible - using E = helper_t<3>; // may throw on move and copy - using C = helper_t<4>; // nothrow copy constructible + using M = helper_t<2>; // nothrow move constructible + using E = helper_t<3>; // may throw on move and copy + using C = helper_t<4>; // nothrow copy constructible + using G = helper_t<33>; // nothrow copy constructible; throwing move constructible + using H = helper_t<40>; // nothrow copy/move constructible; throwing copy/move assignable static_assert(not std::is_nothrow_copy_constructible_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); static_assert(std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_copy_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); + static_assert(not std::is_nothrow_copy_assignable_v); + static_assert(not std::is_nothrow_move_assignable_v); SECTION("from rval") { @@ -3198,14 +3647,14 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { T a(std::in_place); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { static_assert(not extension || std::is_nothrow_assignable_v &&>); T a(std::in_place); a = unexpected(5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -3227,7 +3676,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { T a(std::in_place); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -3248,7 +3697,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(not std::is_nothrow_assignable_v &&>); T a(std::in_place); a = unexpected(5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -3264,6 +3713,16 @@ TEST_CASE("expected void", "[expected_void][polyfill]") #endif } } + +#ifndef _MSC_VER + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(std::in_place); + a = unexpected(11); + CHECK(a.error().v == 11); + } +#endif } SECTION("nothrow copy") @@ -3274,7 +3733,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { T a(std::in_place); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); } { @@ -3295,7 +3754,15 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(not std::is_nothrow_assignable_v &&>); T a(std::in_place); a = unexpected(5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); + } + + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v &&>); + T a(std::in_place); + a = unexpected(11); + CHECK(a.error() == 11); } { @@ -3349,10 +3816,36 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(unexpect, 3); a = T(unexpect, 5); - CHECK(a.error().v == 5 * helper::from_rval); + CHECK(a.error().v == 5 * from_rval); a = unexpected(7); - CHECK(a.error().v == 7 * helper::from_rval); + CHECK(a.error().v == 7 * from_rval); + + { + using T = expected; +#ifndef _MSC_VER + static_assert(not extension || std::is_nothrow_assignable_v &&>); +#endif + T a(unexpect, 0); + a = unexpected(11); + CHECK(a.error().v == 11 * from_rval); + } + + { // the rvalue conversion-assignment operator propagates a throwing E::operator= + using T = expected; + static_assert(not std::is_nothrow_assignable_v &&>); + T a(unexpect, 7); + a = unexpected(11); + CHECK(a.error().v == 11 * from_rval); + + try { + a = unexpected({0.0}); + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error().v == 11 * from_rval); + } + } } SECTION("constexpr") @@ -3416,7 +3909,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(std::in_place); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -3439,7 +3932,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(std::in_place); unexpected const b(5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -3467,7 +3960,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(std::in_place); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -3490,7 +3983,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(std::in_place); unexpected const b(5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -3507,6 +4000,17 @@ TEST_CASE("expected void", "[expected_void][polyfill]") #endif } } + +#ifndef _MSC_VER + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(std::in_place); + unexpected const b(11); + a = b; + CHECK(a.error().v == 11); + } +#endif } SECTION("nothrow copy") @@ -3518,7 +4022,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(std::in_place); T const b(unexpect, 5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); } { @@ -3537,7 +4041,16 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(std::in_place); unexpected const b(5); a = b; - CHECK(a.error().v == 5 * helper::from_lval_const); + CHECK(a.error().v == 5 * from_lval_const); + } + + { + using T = expected; + static_assert(not extension || std::is_nothrow_assignable_v const &>); + T a(std::in_place); + unexpected const b(11); + a = b; + CHECK(a.error() == 11); } { @@ -3578,17 +4091,46 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T const b(unexpect, 5); a = b; #ifndef PFN_TEST_VALIDATION - CHECK((a.has_error() && a.error().v == 5 * helper::from_lval_const)); + CHECK((a.has_error() && a.error().v == 5 * from_lval_const)); #else - CHECK((not a.has_value() && a.error().v == 5 * helper::from_lval_const)); + CHECK((not a.has_value() && a.error().v == 5 * from_lval_const)); #endif unexpected const c(7); a = c; #ifndef PFN_TEST_VALIDATION - CHECK((a.has_error() && a.error().v == 7 * helper::from_lval_const)); + CHECK((a.has_error() && a.error().v == 7 * from_lval_const)); #else - CHECK((not a.has_value() && a.error().v == 7 * helper::from_lval_const)); + CHECK((not a.has_value() && a.error().v == 7 * from_lval_const)); #endif + + { + using T = expected; +#ifndef _MSC_VER + static_assert(not extension || std::is_nothrow_assignable_v const &>); +#endif + T a(unexpect, 0); + unexpected const u(7); + a = u; + CHECK(a.error().v == 7 * from_rval); + } + + { // the const-lvalue conversion-assignment operator propagates a throwing E::operator= + using T = expected; + static_assert(not std::is_nothrow_assignable_v const &>); + T a(unexpect, 7); + unexpected const b(11); + a = b; + CHECK(a.error().v == 11 * from_lval_const); + + try { + unexpected const c({0.0}); + a = c; + FAIL(); + } catch (std::runtime_error const &e) { + CHECK(std::strcmp(e.what(), "invalid input") == 0); + CHECK(a.error().v == 11 * from_lval_const); + } + } } } @@ -3765,6 +4307,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(not std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); SUCCEED(); } @@ -3780,11 +4323,12 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(not std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); SUCCEED(); } - SECTION("swappabla, non-nothrow-move-constructible") + SECTION("swappable, non-nothrow-move-constructible") { struct A : swappable { A(A &&) noexcept(false) {} @@ -3795,36 +4339,39 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(not std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); SUCCEED(); } - SECTION("nothrow-swappabla, non-nothrow-move-constructible") + SECTION("nothrow-swappable, nothrow-move-constructible") { struct A : nothrow_swappable { - A(A &&) noexcept(false) {} + A(A &&) noexcept(true) = default; }; static_assert(std::is_swappable_v); static_assert(std::is_nothrow_swappable_v); static_assert(std::is_move_constructible_v); - static_assert(not std::is_nothrow_move_constructible_v); + static_assert(std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(is_nothrow_swappable>); SUCCEED(); } - SECTION("nothrow-swappabla, nothrow-move-constructible") + SECTION("swappable, nothrow-move-constructible") { - struct A : nothrow_swappable { + struct A : swappable { A(A &&) noexcept(true) = default; }; static_assert(std::is_swappable_v); - static_assert(std::is_nothrow_swappable_v); + static_assert(not std::is_nothrow_swappable_v); static_assert(std::is_move_constructible_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(is_swappable>); + static_assert(not is_nothrow_swappable>); SUCCEED(); } @@ -3851,8 +4398,8 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T a(unexpect, 17); T b(unexpect, 23); swap(a, b); - CHECK(a.error().v == 23 * helper::swapped); - CHECK(b.error().v == 17 * helper::swapped); + CHECK(a.error().v == 23 * swapped); + CHECK(b.error().v == 17 * swapped); } } @@ -3864,9 +4411,9 @@ TEST_CASE("expected void", "[expected_void][polyfill]") swap(a, b); CHECK(a.has_value()); #ifndef PFN_TEST_VALIDATION - CHECK((b.has_error() && b.error().v == 19 * helper::from_rval)); + CHECK((b.has_error() && b.error().v == 19 * from_rval)); #else - CHECK((not b.has_value() && b.error().v == 19 * helper::from_rval)); + CHECK((not b.has_value() && b.error().v == 19 * from_rval)); #endif } @@ -3879,9 +4426,9 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T b(unexpect, 29); swap(a, b); #ifndef PFN_TEST_VALIDATION - CHECK((a.has_error() && a.error().v == 29 * helper::from_rval)); + CHECK((a.has_error() && a.error().v == 29 * from_rval)); #else - CHECK((not a.has_value() && a.error().v == 29 * helper::from_rval)); + CHECK((not a.has_value() && a.error().v == 29 * from_rval)); #endif CHECK(b.has_value()); } @@ -3898,9 +4445,9 @@ TEST_CASE("expected void", "[expected_void][polyfill]") T b(unexpect, 11); swap(a, b); #ifndef PFN_TEST_VALIDATION - CHECK((a.has_error() && a.error().v == 11 * helper::from_rval)); + CHECK((a.has_error() && a.error().v == 11 * from_rval)); #else - CHECK((not a.has_value() && a.error().v == 11 * helper::from_rval)); + CHECK((not a.has_value() && a.error().v == 11 * from_rval)); #endif CHECK(b.has_value()); } @@ -4038,31 +4585,31 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { helper b{1}; - CHECK((b = a.error()).v == 17 * helper::from_lval); - CHECK((b = std::as_const(a).error()).v == 17 * helper::from_lval_const); - CHECK((b = std::move(std::as_const(a)).error()).v == 17 * helper::from_rval_const); - CHECK((b = std::move(a).error()).v == 17 * helper::from_rval); + CHECK((b = a.error()).v == 17 * from_lval); + CHECK((b = std::as_const(a).error()).v == 17 * from_lval_const); + CHECK((b = std::move(std::as_const(a)).error()).v == 17 * from_rval_const); + CHECK((b = std::move(a).error()).v == 17 * from_rval); } } SECTION("error_or") { using T = expected; - SECTION("noexcept extension") + SECTION("noexcept") { static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); } SECTION("error") { T a(unexpect, 7); - CHECK(a.error_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::as_const(a).error_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::move(std::as_const(a)).error_or(0) == helper(7 * helper::from_lval_const)); - CHECK(std::move(a).error_or(0) == helper(7 * helper::from_rval)); + CHECK(a.error_or(0) == helper(7 * from_lval_const)); + CHECK(std::as_const(a).error_or(0) == helper(7 * from_lval_const)); + CHECK(std::move(std::as_const(a)).error_or(0) == helper(7 * from_lval_const)); + CHECK(std::move(a).error_or(0) == helper(7 * from_rval)); } SECTION("value") @@ -4076,10 +4623,10 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { T const a{}; helper b(11); - CHECK(a.error_or(b) == helper(11 * helper::from_lval)); - CHECK(a.error_or(std::as_const(b)) == helper(11 * helper::from_lval_const)); - CHECK(a.error_or(std::move(std::as_const(b))) == helper(11 * helper::from_rval_const)); - CHECK(a.error_or(std::move(b)) == helper(11 * helper::from_rval)); + CHECK(a.error_or(b) == helper(11 * from_lval)); + CHECK(a.error_or(std::as_const(b)) == helper(11 * from_lval_const)); + CHECK(a.error_or(std::move(std::as_const(b))) == helper(11 * from_rval_const)); + CHECK(a.error_or(std::move(b)) == helper(11 * from_rval)); } } @@ -4090,12 +4637,12 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(not std::is_nothrow_copy_constructible_v); static_assert(std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_convertible_v); - static_assert(std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); } { @@ -4103,12 +4650,12 @@ TEST_CASE("expected void", "[expected_void][polyfill]") static_assert(std::is_nothrow_copy_constructible_v); static_assert(not std::is_nothrow_move_constructible_v); static_assert(not std::is_nothrow_convertible_v); - static_assert(std::is_nothrow_convertible_v); + static_assert(std::is_nothrow_convertible_v); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not noexcept(std::declval().error_or(std::declval()))); + static_assert(not noexcept(std::declval().error_or(std::declval()))); static_assert(not noexcept(std::declval().error_or(std::declval()))); - static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); + static_assert(not extension || noexcept(std::declval().error_or(std::declval()))); } SUCCEED(); @@ -4117,18 +4664,18 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; SECTION("lval const") { { constexpr T a(unexpect, {3.0}, 5); - static_assert(a.error_or(c).v == 3 * 5 * helper::from_lval_const); + static_assert(a.error_or(c).v == 3 * 5 * from_lval_const); } { constexpr T a(std::in_place); - static_assert(a.error_or(c).v == 7 * helper::from_lval_const); + static_assert(a.error_or(c).v == 7 * from_lval_const); } SUCCEED(); @@ -4136,9 +4683,9 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("rval") { - static_assert(T{unexpect, {3.0}, 5}.error_or(c).v == 3 * 5 * helper::from_rval); - static_assert(T{std::in_place}.error_or(c).v == 7 * helper::from_lval_const); - static_assert(T{std::in_place}.error_or(helper(helper::list_t{7.0}, 3)).v == 7 * 3 * helper::from_rval); + static_assert(T{unexpect, {3.0}, 5}.error_or(c).v == 3 * 5 * from_rval); + static_assert(T{std::in_place}.error_or(c).v == 7 * from_lval_const); + static_assert(T{std::in_place}.error_or(helper(helper_list_t{7.0}, 3)).v == 7 * 3 * from_rval); SUCCEED(); } @@ -4165,10 +4712,10 @@ TEST_CASE("expected void", "[expected_void][polyfill]") constexpr auto fn = []() constexpr -> expected { return {0}; }; T a(unexpect, 11); - CHECK(a.and_then(fn).error().v == 11 * helper::from_lval); - CHECK(std::as_const(a).and_then(fn).error().v == 11 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).and_then(fn).error().v == 11 * helper::from_rval_const); - CHECK(std::move(a).and_then(fn).error().v == 11 * helper::from_rval); + CHECK(a.and_then(fn).error().v == 11 * from_lval); + CHECK(std::as_const(a).and_then(fn).error().v == 11 * from_lval_const); + CHECK(std::move(std::as_const(a)).and_then(fn).error().v == 11 * from_rval_const); + CHECK(std::move(a).and_then(fn).error().v == 11 * from_rval); } SECTION("constexpr") @@ -4211,10 +4758,10 @@ TEST_CASE("expected void", "[expected_void][polyfill]") }; T a(unexpect, 5); - CHECK(a.or_else(fn).error() == 5 * 3 * helper::from_lval); - CHECK(std::as_const(a).or_else(fn).error() == 5 * 3 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).or_else(fn).error() == 5 * 3 * helper::from_rval_const); - CHECK(std::move(a).or_else(fn).error() == 5 * 3 * helper::from_rval); + CHECK(a.or_else(fn).error() == 5 * 3 * from_lval); + CHECK(std::as_const(a).or_else(fn).error() == 5 * 3 * from_lval_const); + CHECK(std::move(std::as_const(a)).or_else(fn).error() == 5 * 3 * from_rval_const); + CHECK(std::move(a).or_else(fn).error() == 5 * 3 * from_rval); } SECTION("value") @@ -4229,7 +4776,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; constexpr auto fn = [](auto &&a) constexpr -> expected { return expected{unexpect, helper(std::forward(a)).v * 3}; }; @@ -4243,7 +4790,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { constexpr T a(unexpect, {3.0}, 5); - static_assert(a.or_else(fn).error() == 3 * 3 * 5 * helper::from_lval_const); + static_assert(a.or_else(fn).error() == 3 * 3 * 5 * from_lval_const); } SUCCEED(); @@ -4251,7 +4798,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("rval") { - static_assert(T{unexpect, {3.0}, 5}.or_else(fn).error() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{unexpect, {3.0}, 5}.or_else(fn).error() == 3 * 3 * 5 * from_rval); static_assert(T{std::in_place}.or_else(fn).has_value()); SUCCEED(); @@ -4285,11 +4832,11 @@ TEST_CASE("expected void", "[expected_void][polyfill]") constexpr auto fn = []() constexpr -> int { return 0; }; T a(unexpect, 11); - CHECK(a.transform(fn).error().v == 11 * helper::from_lval); - CHECK(std::as_const(a).transform(fn).error().v == 11 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).transform(fn).error().v == 11 * helper::from_rval_const); - CHECK(std::move(a).transform(fn).error().v == 11 * helper::from_rval); - CHECK(a.transform([]() {}).error().v == 11 * helper::from_lval); + CHECK(a.transform(fn).error().v == 11 * from_lval); + CHECK(std::as_const(a).transform(fn).error().v == 11 * from_lval_const); + CHECK(std::move(std::as_const(a)).transform(fn).error().v == 11 * from_rval_const); + CHECK(std::move(a).transform(fn).error().v == 11 * from_rval); + CHECK(a.transform([]() {}).error().v == 11 * from_lval); } SECTION("constexpr") @@ -4330,10 +4877,10 @@ TEST_CASE("expected void", "[expected_void][polyfill]") constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; T a(unexpect, 5); - CHECK(a.transform_error(fn).error() == 5 * 3 * helper::from_lval); - CHECK(std::as_const(a).transform_error(fn).error() == 5 * 3 * helper::from_lval_const); - CHECK(std::move(std::as_const(a)).transform_error(fn).error() == 5 * 3 * helper::from_rval_const); - CHECK(std::move(a).transform_error(fn).error() == 5 * 3 * helper::from_rval); + CHECK(a.transform_error(fn).error() == 5 * 3 * from_lval); + CHECK(std::as_const(a).transform_error(fn).error() == 5 * 3 * from_lval_const); + CHECK(std::move(std::as_const(a)).transform_error(fn).error() == 5 * 3 * from_rval_const); + CHECK(std::move(a).transform_error(fn).error() == 5 * 3 * from_rval); } SECTION("value") @@ -4348,7 +4895,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("constexpr") { using T = expected; - constexpr helper c{helper::list_t(), 7}; + constexpr helper c{helper_list_t(), 7}; constexpr auto fn = [](auto &&a) constexpr -> int { return helper(std::forward(a)).v * 3; }; SECTION("lval const") @@ -4360,7 +4907,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") { constexpr T a(unexpect, {3.0}, 5); - static_assert(a.transform_error(fn).error() == 3 * 3 * 5 * helper::from_lval_const); + static_assert(a.transform_error(fn).error() == 3 * 3 * 5 * from_lval_const); } SUCCEED(); @@ -4368,7 +4915,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SECTION("rval") { - static_assert(T{unexpect, {3.0}, 5}.transform_error(fn).error() == 3 * 3 * 5 * helper::from_rval); + static_assert(T{unexpect, {3.0}, 5}.transform_error(fn).error() == 3 * 3 * 5 * from_rval); static_assert(T{std::in_place}.transform_error(fn).has_value()); SUCCEED(); @@ -4457,7 +5004,7 @@ TEST_CASE("expected void", "[expected_void][polyfill]") CHECK((t1 != u1)); constexpr T t2{unexpect, {3.0}, 4}; - constexpr U u2{std::in_place, helper::list_t(), 3, 2, 2}; + constexpr U u2{std::in_place, helper_list_t(), 3, 2, 2}; static_assert(t2 == u2); static_assert(not(t2 != u2)); CHECK((t1 == t2)); @@ -4473,6 +5020,21 @@ TEST_CASE("expected void", "[expected_void][polyfill]") SUCCEED(); } + + SECTION("value state") + { + using T = expected; + using U = unexpected; + T const t1{std::in_place}; + U const u1{std::in_place, {12.0}}; + CHECK(not(t1 == u1)); + CHECK((t1 != u1)); + + constexpr T t2{std::in_place}; + constexpr U u2{std::in_place, helper_list_t(), 3, 2, 2}; + static_assert(not(t2 == u2)); + static_assert(t2 != u2); + } } } } diff --git a/tests/util/helper_types.hpp b/tests/util/helper_types.hpp index 2ff60899..18e9a072 100644 --- a/tests/util/helper_types.hpp +++ b/tests/util/helper_types.hpp @@ -10,20 +10,22 @@ #include #include +// Use prime numbers to record Foo states in witness +enum helper_witness { + from_lval = 53, // + from_lval_const = 59, + from_rval = 61, + from_rval_const = 67, + swapped = 97 +}; + +using helper_list_t = std::initializer_list; + template struct helper_t { static inline int state = 0; int v = {}; - // Use prime numbers to record Foo states in witness - enum { - from_lval = 53, // - from_lval_const = 59, - from_rval = 61, - from_rval_const = 67, - swapped = 97 - }; - // No default constructor helper_t() = delete; @@ -32,29 +34,45 @@ template struct helper_t { constexpr bool operator==(helper_t const &) const noexcept = default; // Assignment operators will multiply witness by a prime - constexpr helper_t &operator=(helper_t &o) noexcept + constexpr helper_t &operator=(helper_t &o) noexcept(V < 40 || V >= 41) { + if constexpr (V >= 40 && V < 41) { + if (o.v == 0) + throw std::runtime_error("invalid input"); + } v = o.v; v *= from_lval; return *this; } - constexpr helper_t &operator=(helper_t const &o) noexcept + constexpr helper_t &operator=(helper_t const &o) noexcept(V < 40 || V >= 41) { + if constexpr (V >= 40 && V < 41) { + if (o.v == 0) + throw std::runtime_error("invalid input"); + } v = o.v; v *= from_lval_const; return *this; } - constexpr helper_t &operator=(helper_t &&o) noexcept + constexpr helper_t &operator=(helper_t &&o) noexcept(V < 40 || V >= 41) { + if constexpr (V >= 40 && V < 41) { + if (o.v == 0) + throw std::runtime_error("invalid input"); + } v = o.v; v *= from_rval; return *this; } - constexpr helper_t &operator=(helper_t const &&o) noexcept + constexpr helper_t &operator=(helper_t const &&o) noexcept(V < 40 || V >= 41) { + if constexpr (V >= 40 && V < 41) { + if (o.v == 0) + throw std::runtime_error("invalid input"); + } v = o.v; v *= from_rval_const; return *this; @@ -106,19 +124,17 @@ template struct helper_t { state += v; } - using list_t = std::initializer_list; - - helper_t(list_t list) noexcept(true) : v(init(list)) { state += v; } + helper_t(helper_list_t list) noexcept(true) : v(init(list)) { state += v; } // Potentially throwing constructor - constexpr helper_t(list_t list, std::integral auto... a) noexcept(true) + constexpr helper_t(helper_list_t list, std::integral auto... a) noexcept(true) requires(sizeof...(a) > 0) : v(init(list, a...)) // { } // ... and the actual exception being thrown - static constexpr int init(list_t l, auto &&...a) noexcept + static constexpr int init(helper_list_t l, auto &&...a) noexcept { double ret = (1 * ... * a); for (auto d : l) { @@ -132,14 +148,15 @@ template struct helper_t { friend std::strong_ordering operator<=>(helper_t, helper_t) = delete; }; -// helper_t -// V is_nothrow_copy_constructible is_nothrow_move_constructible -// 0 1 1 -// 1 1 1 -// 2 0 1 -// 3 0 0 -// 4 1 0 -// 5 1 1 +// helper_t +// V nothrow_copy_ctor nothrow_move_ctor nothrow_copy_assign nothrow_move_assign +// 0 1 1 1 1 +// 1 1 1 1 1 +// 2 0 1 1 1 +// 3 0 0 1 1 +// 4 1 0 1 1 +// 5 1 1 1 1 +// 40 1 1 0 0 static_assert(std::is_nothrow_copy_constructible_v>); static_assert(std::is_nothrow_move_constructible_v>); static_assert(std::is_nothrow_copy_constructible_v>); @@ -152,13 +169,17 @@ static_assert(std::is_nothrow_copy_constructible_v>); static_assert(not std::is_nothrow_move_constructible_v>); static_assert(std::is_nothrow_copy_constructible_v>); static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_copy_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(not std::is_nothrow_copy_assignable_v>); +static_assert(not std::is_nothrow_move_assignable_v>); // Swap will also multiply witness by a prime template constexpr void swap(helper_t &l, helper_t &r) { std::swap(l.v, r.v); - l.v *= l.swapped; - r.v *= r.swapped; + l.v *= swapped; + r.v *= swapped; } using helper = helper_t<0>;