diff --git a/README.md b/README.md index 2cde897..8433b67 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Implemented LWG Issues: - [x] [LWG-3940](https://wg21.link/lwg3940) `std::expected::value()` also needs `E` to be copy constructible - [x] [LWG-4026](https://wg21.link/lwg4026) Assignment operators of `std::expected` should propagate triviality - [x] [LWG-3877](https://wg21.link/lwg3877) incorrect constraints on const-qualified monadic overloads for `std::expected` +- [x] [LWG-3886](https://wg21.link/lwg3886) Monad mo' problems Enhancements: diff --git a/include/zeus/expected.hpp b/include/zeus/expected.hpp index 4d0943b..d2cd9e5 100644 --- a/include/zeus/expected.hpp +++ b/include/zeus/expected.hpp @@ -942,7 +942,10 @@ struct copy_assign_base : move_ctor_base copy_assign_base(copy_assign_base &&rhs) = default; constexpr copy_assign_base &operator=(const copy_assign_base &rhs) // - noexcept(std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_assignable_v) + noexcept( + std::is_nothrow_copy_constructible_v && std::is_nothrow_copy_assignable_v && std::is_nothrow_copy_constructible_v && + std::is_nothrow_copy_assignable_v + ) { if (this->m_has_val && rhs.m_has_val) { @@ -1063,7 +1066,10 @@ struct move_assign_base : copy_assign_base move_assign_base &operator=(const move_assign_base &rhs) = default; constexpr move_assign_base &operator=(move_assign_base &&rhs) // - noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v && std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v) + noexcept( + std::is_nothrow_move_constructible_v && std::is_nothrow_move_assignable_v && std::is_nothrow_move_constructible_v && + std::is_nothrow_move_assignable_v + ) { if (this->m_has_val && rhs.m_has_val) { @@ -1194,6 +1200,9 @@ class expected typedef E error_type; typedef unexpected unexpected_type; + template + using rebind = expected; + // default constructors constexpr expected() = default; @@ -1286,7 +1295,10 @@ class expected // expected(U &&) // implicit - template> * = nullptr, expected_detail::enable_forward_t * = nullptr> + template< + class U = std::remove_cv_t, + std::enable_if_t> * = nullptr, + expected_detail::enable_forward_t * = nullptr> constexpr expected(U &&v) noexcept(std::is_nothrow_constructible_v) : expected(std::in_place, std::forward(v)) { @@ -1294,7 +1306,7 @@ class expected // explicit template< - class U = T, + class U = std::remove_cv_t, std::enable_if_t> * = nullptr, expected_detail::enable_forward_t * = nullptr> constexpr explicit expected(U &&v) noexcept(std::is_nothrow_constructible_v) @@ -1418,7 +1430,7 @@ class expected // assignments template< - class U = T, // + class U = std::remove_cv_t, std::enable_if_t< // !std::is_same_v> && // !expected_detail::is_specialization_v, unexpected> && // @@ -1544,14 +1556,15 @@ class expected } template - constexpr - std::enable_if_t< + constexpr std::enable_if_t< std::is_swappable_v && std::is_swappable_v && // std::is_move_constructible_v && std::is_move_constructible_v && // (std::is_nothrow_move_constructible_v || std::is_nothrow_move_constructible_v)> swap(expected &rhs) // - noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v && // - std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v) + noexcept( + std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v && // + std::is_nothrow_move_constructible_v && std::is_nothrow_swappable_v + ) { using std::swap; if (this->m_has_val && rhs.m_has_val) @@ -1688,7 +1701,7 @@ class expected return std::move(err()); } - template + template> constexpr T value_or(U &&v) const & // noexcept(std::is_nothrow_copy_constructible_v && expected_detail::is_nothrow_convertible_v) { @@ -1703,7 +1716,7 @@ class expected return static_cast(std::forward(v)); } } - template + template> constexpr T value_or(U &&v) && // noexcept(std::is_nothrow_move_constructible_v && expected_detail::is_nothrow_convertible_v) { @@ -2143,6 +2156,9 @@ class expected typedef E error_type; typedef unexpected unexpected_type; + template + using rebind = expected; + constexpr expected() = default; constexpr expected(const expected &rhs) = default; constexpr expected(expected &&rhs) = default; diff --git a/tests/test_expected/CMakeLists.txt b/tests/test_expected/CMakeLists.txt index 5bd969a..157597f 100644 --- a/tests/test_expected/CMakeLists.txt +++ b/tests/test_expected/CMakeLists.txt @@ -5,6 +5,7 @@ set(SOURCES monadic_tests.cpp noexcept_tests.cpp equality_tests.cpp + lwg_3886_tests.cpp test_expected_main.cpp ) diff --git a/tests/test_expected/lwg_3886_tests.cpp b/tests/test_expected/lwg_3886_tests.cpp new file mode 100644 index 0000000..caf48dd --- /dev/null +++ b/tests/test_expected/lwg_3886_tests.cpp @@ -0,0 +1,72 @@ +#include + +#include + +using namespace zeus; + +namespace +{ + +struct ConstructModeDistinction +{ + enum ConstructedMode + { + kDefault, + kCopy, + kMove, + kCopyAssign, + kMoveAssign, + }; + + constexpr ConstructModeDistinction() noexcept + : mode(kDefault) + { + } + constexpr ConstructModeDistinction(ConstructModeDistinction const&) noexcept + : mode(kCopy) + { + } + constexpr ConstructModeDistinction(ConstructModeDistinction&&) noexcept + : mode(kMove) + { + } + constexpr ConstructModeDistinction& operator=(ConstructModeDistinction&&) noexcept + { + mode = kMoveAssign; + return *this; + } + constexpr ConstructModeDistinction& operator=(ConstructModeDistinction const&) noexcept + { + mode = kCopyAssign; + return *this; + } + + ConstructedMode mode; +}; + +} // namespace + +SCENARIO("Monad mo' problems", "[LWG-3886]") +{ + using T = const ConstructModeDistinction; + using E = int; + + // FIXME LWG-3891 + //SECTION("constructor") + //{ + // expected const e({}); + // CHECK(e.value().mode == T::kMove); + //} + //SECTION("assignment") + //{ + // expected e {zeus::unexpect}; + // e = {}; + // CHECK(e.value().mode == T::kMove); + //} + + SECTION("value_or()") + { + expected const e {zeus::unexpect}; + CHECK(e.value_or({}).mode == T::kMove); + } +} diff --git a/tests/third_party/msvc/CMakeLists.txt b/tests/third_party/msvc/CMakeLists.txt index bb3df82..7126488 100644 --- a/tests/third_party/msvc/CMakeLists.txt +++ b/tests/third_party/msvc/CMakeLists.txt @@ -1,3 +1,5 @@ +include_directories(SYSTEM "include") + add_subdirectory(msvc_stl_p0323r12_test) if (MSVC_TOOLSET_VERSION GREATER_EQUAL 143) # can only built with v143 and later diff --git a/tests/third_party/msvc/include/is_permissive.hpp b/tests/third_party/msvc/include/is_permissive.hpp new file mode 100644 index 0000000..007ae88 --- /dev/null +++ b/tests/third_party/msvc/include/is_permissive.hpp @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#pragma once + +#include + +namespace detail { + constexpr bool permissive() { + return false; + } + + template + struct PermissiveTestBase { + static constexpr bool permissive() { + return true; + } + }; + + template + struct PermissiveTest : PermissiveTestBase { + static constexpr bool test() { + return permissive(); + } + }; +} // namespace detail + +template +constexpr bool is_permissive_v = detail::PermissiveTest::test(); + +_INLINE_VAR constexpr bool is_permissive = is_permissive_v; diff --git a/tests/third_party/msvc/msvc_stl_p0323r12_test/msvc_stl_p0323r12_test.cpp b/tests/third_party/msvc/msvc_stl_p0323r12_test/msvc_stl_p0323r12_test.cpp index b22f72c..4807440 100644 --- a/tests/third_party/msvc/msvc_stl_p0323r12_test/msvc_stl_p0323r12_test.cpp +++ b/tests/third_party/msvc/msvc_stl_p0323r12_test/msvc_stl_p0323r12_test.cpp @@ -1,8 +1,6 @@ // Copyright (c) Microsoft Corporation. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -#define _CONTAINER_DEBUG_LEVEL 1 - #include #include #include @@ -10,6 +8,8 @@ #include #include +#include // noexcept operator possibly gives wrong values in permissive modes + #include using namespace zeus; @@ -23,6 +23,7 @@ using std::is_default_constructible_v; using std::is_constructible_v; using std::is_copy_constructible_v; using std::is_move_constructible_v; +using std::is_trivially_default_constructible_v; using std::is_trivially_constructible_v; using std::is_trivially_copy_constructible_v; using std::is_trivially_move_constructible_v; @@ -30,14 +31,23 @@ using std::is_trivially_move_constructible_v; using std::is_copy_assignable_v; using std::is_move_assignable_v; using std::is_destructible_v; +using std::is_trivially_copyable_v; using std::is_trivially_copy_assignable_v; using std::is_trivially_move_assignable_v; using std::is_trivially_destructible_v; using std::is_base_of_v; +using std::is_invocable_v; +using std::is_assignable_v; +using std::is_const_v; +using std::is_volatile_v; using std::in_place; +using std::remove_cvref_t; +using std::remove_reference_t; +using std::remove_volatile_t; + using std::copyable; using std::initializer_list; @@ -118,31 +128,31 @@ namespace test_unexpected { // [expected.un.ctor] const int& input = 1; Unexpect in_place_lvalue_constructed{in_place, input}; - static_assert(noexcept(Unexpect{in_place, input}) == copy_construction_is_noexcept); + static_assert(noexcept(Unexpect{in_place, input}) == copy_construction_is_noexcept || is_permissive); assert(in_place_lvalue_constructed == Unexpect{test_error{1}}); Unexpect in_place_rvalue_constructed{in_place, 42}; - static_assert(noexcept(Unexpect{in_place, 42}) == move_construction_is_noexcept); + static_assert(noexcept(Unexpect{in_place, 42}) == move_construction_is_noexcept || is_permissive); assert(in_place_rvalue_constructed == Unexpect{test_error{42}}); Unexpect in_place_ilist_lvalue_constructed{in_place, {2}, input}; - static_assert(noexcept(Unexpect{in_place, {2}, input}) == copy_construction_is_noexcept); + static_assert(noexcept(Unexpect{in_place, {2}, input}) == copy_construction_is_noexcept || is_permissive); assert(in_place_ilist_lvalue_constructed == Unexpect{test_error{1}}); Unexpect in_place_ilist_rvalue_constructed{in_place, {2}, 1337}; - static_assert(noexcept(Unexpect{in_place, {2}, 1337}) == move_construction_is_noexcept); + static_assert(noexcept(Unexpect{in_place, {2}, 1337}) == move_construction_is_noexcept || is_permissive); assert(in_place_ilist_rvalue_constructed == Unexpect{test_error{1337}}); Unexpect base_error_constructed{test_error{3}}; - static_assert(noexcept(Unexpect{test_error{3}}) == move_construction_is_noexcept); + static_assert(noexcept(Unexpect{test_error{3}}) == move_construction_is_noexcept || is_permissive); assert(base_error_constructed.error()._val == 3); Unexpect conversion_error_constructed{convertible{4}}; - static_assert(noexcept(Unexpect{convertible{4}}) == move_construction_is_noexcept); + static_assert(noexcept(Unexpect{convertible{4}}) == move_construction_is_noexcept || is_permissive); assert(conversion_error_constructed.error()._val == 4); Unexpect brace_error_constructed{{5}}; - static_assert(noexcept(Unexpect{{5}}) == move_construction_is_noexcept); + static_assert(noexcept(Unexpect{{5}}) == move_construction_is_noexcept || is_permissive); assert(brace_error_constructed.error()._val == 5); // [expected.un.eq] @@ -207,7 +217,8 @@ namespace test_unexpected { namespace test_unexpect { auto copy = unexpect; static_assert(is_same_v); - static_assert(is_trivial_v); + static_assert(is_trivially_copyable_v); + static_assert(is_trivially_default_constructible_v); static_assert(is_empty_v); } // namespace test_unexpect @@ -220,20 +231,18 @@ namespace test_expected { using Expected = expected; static_assert(is_same_v); static_assert(is_same_v); - static_assert(is_same_v> ); + static_assert(is_same_v>); - // FIXME rebind - //static_assert(is_same_v, expected>); + static_assert(is_same_v, expected>); } { using Expected = expected; static_assert(is_same_v); static_assert(is_same_v); - static_assert(is_same_v> ); + static_assert(is_same_v>); - // FIXME rebind - //static_assert(is_same_v, expected>); + static_assert(is_same_v, expected>); } } @@ -445,9 +454,9 @@ namespace test_expected { template struct payload_destructor { constexpr payload_destructor(bool& destructor_called) : _destructor_called(destructor_called) {} - // clang-format off - constexpr ~payload_destructor() requires (IsYes(triviallyDestructible)) = default; - // clang-format on + constexpr ~payload_destructor() + requires (IsYes(triviallyDestructible)) + = default; constexpr ~payload_destructor() { _destructor_called = true; } @@ -511,14 +520,14 @@ namespace test_expected { struct payload_constructors { payload_constructors() = default; // Note clang does not accept local variables in explicit - constexpr explicit(IsYes(explicitConstructible)) - payload_constructors(const convertible&) noexcept(should_be_noexcept) + constexpr explicit(IsYes(explicitConstructible)) payload_constructors(const convertible&) + noexcept(should_be_noexcept) : _val(3) {} - constexpr explicit(IsYes(explicitConstructible)) - payload_constructors(convertible&&) noexcept(should_be_noexcept) + constexpr explicit(IsYes(explicitConstructible)) payload_constructors(convertible&&) + noexcept(should_be_noexcept) : _val(42) {} - constexpr explicit(IsYes(explicitConstructible)) - payload_constructors(initializer_list&, convertible) noexcept(should_be_noexcept) + constexpr explicit(IsYes(explicitConstructible)) payload_constructors(initializer_list&, convertible) + noexcept(should_be_noexcept) : _val(1337) {} [[nodiscard]] constexpr bool operator==(const int val) const noexcept { @@ -538,12 +547,12 @@ namespace test_expected { const Expected copy_constructed_value{const_input_value}; assert(copy_constructed_value); assert(copy_constructed_value.value() == 3); - static_assert(noexcept(Expected{const_input_value}) == should_be_noexcept); + static_assert(noexcept(Expected{const_input_value}) == should_be_noexcept || is_permissive); const Expected move_constructed_value{Input{}}; assert(move_constructed_value); assert(move_constructed_value.value() == 42); - static_assert(noexcept(Expected{Input{}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{}}) == should_be_noexcept || is_permissive); const Expected brace_constructed_value{{}}; assert(brace_constructed_value); @@ -566,7 +575,7 @@ namespace test_expected { const Expected move_constructed_value{Input{in_place}}; assert(move_constructed_value); assert(move_constructed_value.value() == 42); - static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept || is_permissive); const Input const_input_error{unexpect}; const Expected copy_constructed_error{const_input_error}; @@ -577,7 +586,7 @@ namespace test_expected { const Expected move_constructed_error{Input{unexpect}}; assert(!move_constructed_error); assert(move_constructed_error.error() == 42); - static_assert(noexcept(Expected{Input{unexpect}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{unexpect}}) == should_be_noexcept || is_permissive); } { // converting from unexpected @@ -588,12 +597,12 @@ namespace test_expected { const Expected copy_constructed{const_input}; assert(!copy_constructed); assert(copy_constructed.error() == 3); - static_assert(noexcept(Expected{const_input}) == should_be_noexcept); + static_assert(noexcept(Expected{const_input}) == should_be_noexcept || is_permissive); const Expected move_constructed{Input{in_place}}; assert(!move_constructed); assert(move_constructed.error() == 42); - static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept || is_permissive); } { // in place payload @@ -606,12 +615,12 @@ namespace test_expected { const Expected value_constructed{in_place, convertible{}}; assert(value_constructed); assert(value_constructed.value() == 42); - static_assert(noexcept(Expected{in_place, convertible{}}) == should_be_noexcept); + static_assert(noexcept(Expected{in_place, convertible{}}) == should_be_noexcept || is_permissive); const Expected ilist_value_constructed{in_place, {1}, convertible{}}; assert(ilist_value_constructed); assert(ilist_value_constructed.value() == 1337); - static_assert(noexcept(Expected{in_place, {1}, convertible{}}) == should_be_noexcept); + static_assert(noexcept(Expected{in_place, {1}, convertible{}}) == should_be_noexcept || is_permissive); } { // in place error @@ -624,12 +633,12 @@ namespace test_expected { const Expected value_constructed{unexpect, convertible{}}; assert(!value_constructed); assert(value_constructed.error() == 42); - static_assert(noexcept(Expected{unexpect, convertible{}}) == should_be_noexcept); + static_assert(noexcept(Expected{unexpect, convertible{}}) == should_be_noexcept || is_permissive); const Expected ilist_value_constructed{unexpect, {1}, convertible{}}; assert(!ilist_value_constructed); assert(ilist_value_constructed.error() == 1337); - static_assert(noexcept(Expected{unexpect, {1}, convertible{}}) == should_be_noexcept); + static_assert(noexcept(Expected{unexpect, {1}, convertible{}}) == should_be_noexcept || is_permissive); } { // expected: converting from different expected @@ -647,7 +656,7 @@ namespace test_expected { const Expected move_constructed_value{Input{in_place}}; assert(move_constructed_value); move_constructed_value.value(); - static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept || is_permissive); const Input const_input_error{unexpect}; const Expected copy_constructed_error{const_input_error}; @@ -658,7 +667,7 @@ namespace test_expected { const Expected move_constructed_error{Input{unexpect}}; assert(!move_constructed_error); assert(move_constructed_error.error() == 42); - static_assert(noexcept(Expected{Input{unexpect}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{unexpect}}) == should_be_noexcept || is_permissive); } { // expected: converting from unexpected @@ -669,12 +678,12 @@ namespace test_expected { const Expected copy_constructed{const_input}; assert(!copy_constructed); assert(copy_constructed.error() == 3); - static_assert(noexcept(Expected{const_input}) == should_be_noexcept); + static_assert(noexcept(Expected{const_input}) == should_be_noexcept || is_permissive); const Expected move_constructed{Input{in_place}}; assert(!move_constructed); assert(move_constructed.error() == 42); - static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept); + static_assert(noexcept(Expected{Input{in_place}}) == should_be_noexcept || is_permissive); } { // expected: in place payload @@ -695,12 +704,12 @@ namespace test_expected { const Expected value_constructed{unexpect, convertible{}}; assert(!value_constructed); assert(value_constructed.error() == 42); - static_assert(noexcept(Expected{unexpect, convertible{}}) == should_be_noexcept); + static_assert(noexcept(Expected{unexpect, convertible{}}) == should_be_noexcept || is_permissive); const Expected ilist_value_constructed{unexpect, {1}, convertible{}}; assert(!ilist_value_constructed); assert(ilist_value_constructed.error() == 1337); - static_assert(noexcept(Expected{unexpect, {1}, convertible{}}) == should_be_noexcept); + static_assert(noexcept(Expected{unexpect, {1}, convertible{}}) == should_be_noexcept || is_permissive); } } @@ -717,10 +726,6 @@ namespace test_expected { expected e1(false); expected e2(e1); assert(!e2.value()); - - expected e3{}; - expected e4(e3); - assert(e4.has_value()); } template vec_; constexpr Data(initializer_list il) : vec_(il) {} @@ -2355,10 +2360,201 @@ void test_lwg_3843() { } } +// Test LWG-3886: "Monad mo' problems (in optional and expected)" + +enum class Qualification { + None, + Const, + Volatile, + ConstVolatile, +}; + +template +constexpr Qualification CvQualOf = + is_const_v> + ? (is_volatile_v> ? Qualification::ConstVolatile : Qualification::Const) + : (is_volatile_v> ? Qualification::Volatile : Qualification::None); + +struct QualDistinction { + QualDistinction() = default; + + constexpr QualDistinction(QualDistinction&&) noexcept : qual_{Qualification::None} {} + constexpr QualDistinction(const QualDistinction&) noexcept : qual_{Qualification::Const} {} + template + requires is_same_v, QualDistinction> + constexpr QualDistinction(T&&) noexcept : qual_{CvQualOf} {} + + constexpr QualDistinction& operator=(QualDistinction&&) noexcept { + qual_ = Qualification::None; + return *this; + } + constexpr QualDistinction& operator=(const QualDistinction&) noexcept { + qual_ = Qualification::Const; + return *this; + } + template + requires is_same_v, QualDistinction> + constexpr QualDistinction& operator=(T&&) noexcept { + qual_ = CvQualOf; + return *this; + } + template + requires is_same_v, QualDistinction> + constexpr const QualDistinction& operator=(T&&) const noexcept { + qual_ = CvQualOf; + return *this; + } + template + requires is_same_v, QualDistinction> + volatile QualDistinction& operator=(T&&) volatile noexcept { + qual_ = CvQualOf; + return *this; + } + template + requires is_same_v, QualDistinction> + const volatile QualDistinction& operator=(T&&) const volatile noexcept { + qual_ = CvQualOf; + return *this; + } + + mutable Qualification qual_ = Qualification::None; +}; + +constexpr bool test_lwg_3886() { + assert((expected{unexpect}.value_or({}).qual_ == Qualification::None)); + { + expected ex{unexpect}; + assert(ex.value_or({}).qual_ == Qualification::None); + } + assert((expected{unexpect} = {QualDistinction{}}).value().qual_ == Qualification::None); + { + expected ex{in_place}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + ex = zeus::unexpected{'*'}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + } + + assert((expected{unexpect}.value_or({}).qual_ == Qualification::None)); + { + expected ex{unexpect}; + assert(ex.value_or({}).qual_ == Qualification::None); + } +#if 0 // TRANSITION, LWG-3891 + assert( + (expected{unexpect} = {QualDistinction{}}).value().qual_ == Qualification::None); + { + expected ex{in_place}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + ex = zeus::unexpected{'*'}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + } +#endif // ^^^ no workaround ^^^ + + return true; +} + +void test_lwg_3886_volatile() { + assert((expected{unexpect}.value_or({}).qual_ == Qualification::None)); + { + expected ex{unexpect}; + assert(ex.value_or({}).qual_ == Qualification::None); + } +#if 0 // TRANSITION, LWG-3891 + assert((expected{unexpect} = {QualDistinction{}}).value().qual_ + == Qualification::None); + { + expected ex{in_place}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + ex = zeus::unexpected{'*'}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + } +#endif // ^^^ no workaround ^^^ + + assert((expected{unexpect}.value_or({}).qual_ == Qualification::None)); + { + expected ex{unexpect}; + assert(ex.value_or({}).qual_ == Qualification::None); + } +#if 0 // TRANSITION, LWG-3891 + assert((expected{unexpect} = {QualDistinction{}}).value().qual_ + == Qualification::None); + { + expected ex{in_place}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + ex = zeus::unexpected{'*'}; + assert((ex = {QualDistinction{}}).value().qual_ == Qualification::None); + } +#endif // ^^^ no workaround ^^^ +} + // Test GH-4011: these predicates triggered constraint recursion. static_assert(copyable>); static_assert(copyable>); +// Test workaround for DevCom-10655311: Class derived from std::expected can't be constructed with bool value type +template +class DerivedFromExpected : private expected { +public: + using expected::expected; + using expected::value; +}; + +static_assert(is_constructible_v, bool>); +static_assert(is_constructible_v, const bool&>); + +constexpr bool test_inherited_constructors() { + DerivedFromExpected wrapped_false_val(false); + assert(!wrapped_false_val.value()); + + constexpr bool true_val = true; + DerivedFromExpected wrapped_true_val(true_val); + assert(wrapped_true_val.value()); + + return true; +} + +static_assert(test_inherited_constructors()); + +// Test GH-4279 "Add deleted function overloads to expected" + +template +struct ambiguating_expected_copy_constructor_caller { + struct const_lvalue_taker { + const_lvalue_taker(const expected&); + }; + + void operator()(expected); + void operator()(const_lvalue_taker); +}; + +template +struct ambiguating_expected_assignment_source { + operator const expected&() &&; + operator expected&&() &&; +}; + +struct move_only { + move_only() = default; + move_only(move_only&&) = default; + move_only& operator=(move_only&&) = default; +}; + +static_assert(is_invocable_v, const expected&>); +static_assert(is_invocable_v, const expected&>); +static_assert( + !is_invocable_v, const expected&>); +static_assert( + !is_invocable_v, const expected&>); + +#ifndef __EDG__ // TRANSITION, VSO-1601179 +static_assert(!is_assignable_v&, ambiguating_expected_assignment_source>); +static_assert(!is_assignable_v&, ambiguating_expected_assignment_source>); +static_assert(!is_assignable_v&, ambiguating_expected_assignment_source>); +static_assert(!is_assignable_v&, ambiguating_expected_assignment_source>); +#endif // ^^^ no workaround ^^^ + +static_assert(test_lwg_3886()); + int main() { test_unexpected::test_all(); static_assert(test_unexpected::test_all()); @@ -2375,4 +2571,7 @@ int main() { test_reinit_regression(); test_lwg_3843(); + test_lwg_3886(); + test_lwg_3886_volatile(); + test_inherited_constructors(); } diff --git a/tests/third_party/msvc/msvc_stl_p2505r5_test/msvc_stl_p2505r5_test.cpp b/tests/third_party/msvc/msvc_stl_p2505r5_test/msvc_stl_p2505r5_test.cpp index 91e6bad..dbc9a02 100644 --- a/tests/third_party/msvc/msvc_stl_p2505r5_test/msvc_stl_p2505r5_test.cpp +++ b/tests/third_party/msvc/msvc_stl_p2505r5_test/msvc_stl_p2505r5_test.cpp @@ -232,8 +232,6 @@ constexpr void test_impl(Expected&& engaged, Expected&& unengaged) { assert(result.value().x == 77); } } - - engaged.transform([](auto...) { return ""; }); } template