diff --git a/stl/inc/expected b/stl/inc/expected index 79e934a52c..f27e3fc700 100644 --- a/stl/inc/expected +++ b/stl/inc/expected @@ -304,7 +304,7 @@ public: } } - template + template > requires (!is_same_v, in_place_t> && !is_same_v, expected> && !_Is_specialization_v, unexpected> && (!is_same_v, bool> @@ -469,7 +469,7 @@ public: && _Trivially_move_constructible_assignable_destructible<_Err> = default; - template + template > requires (!is_same_v, expected> && !_Is_specialization_v, unexpected> && is_constructible_v<_Ty, _Uty> && is_assignable_v<_Ty&, _Uty> && (is_nothrow_constructible_v<_Ty, _Uty> || is_nothrow_move_constructible_v<_Ty> @@ -730,32 +730,38 @@ public: return _STD move(_Unexpected); } - template - _NODISCARD constexpr _Ty value_or(_Uty&& _Other) const& noexcept( - is_nothrow_copy_constructible_v<_Ty> && is_nothrow_convertible_v<_Uty, _Ty>) /* strengthened */ { - static_assert( - is_copy_constructible_v<_Ty>, "is_copy_constructible_v must be true. (N4950 [expected.object.obs]/18)"); - static_assert( - is_convertible_v<_Uty, _Ty>, "is_convertible_v must be true. (N4950 [expected.object.obs]/18)"); + template > + _NODISCARD constexpr remove_cv_t<_Ty> value_or(_Uty&& _Other) const& noexcept( + is_nothrow_convertible_v> + && is_nothrow_convertible_v<_Uty, remove_cv_t<_Ty>>) /* strengthened */ { + static_assert(is_convertible_v>, + "is_convertible_v> must be true. " + "(N5001 [expected.object.obs]/18 as modified by LWG-3424)"); + static_assert(is_convertible_v<_Uty, remove_cv_t<_Ty>>, + "is_convertible_v> must be true. " + "(N5001 [expected.object.obs]/18 as modified by LWG-3424)"); if (_Has_value) { return _Value; } else { - return static_cast<_Ty>(_STD forward<_Uty>(_Other)); + return static_cast>(_STD forward<_Uty>(_Other)); } } - template - _NODISCARD constexpr _Ty value_or(_Uty&& _Other) && noexcept( - is_nothrow_move_constructible_v<_Ty> && is_nothrow_convertible_v<_Uty, _Ty>) /* strengthened */ { - static_assert( - is_move_constructible_v<_Ty>, "is_move_constructible_v must be true. (N4950 [expected.object.obs]/20)"); - static_assert( - is_convertible_v<_Uty, _Ty>, "is_convertible_v must be true. (N4950 [expected.object.obs]/20)"); + template > + _NODISCARD constexpr remove_cv_t<_Ty> value_or(_Uty&& _Other) && noexcept( + is_nothrow_convertible_v<_Ty, remove_cv_t<_Ty>> + && is_nothrow_convertible_v<_Uty, remove_cv_t<_Ty>>) /* strengthened */ { + static_assert(is_convertible_v<_Ty, remove_cv_t<_Ty>>, + "is_convertible_v> must be true. " + "(N5001 [expected.object.obs]/20 as modified by LWG-3424)"); + static_assert(is_convertible_v<_Uty, remove_cv_t<_Ty>>, + "is_convertible_v> must be true. " + "(N5001 [expected.object.obs]/20 as modified by LWG-3424)"); if (_Has_value) { return _STD move(_Value); } else { - return static_cast<_Ty>(_STD forward<_Uty>(_Other)); + return static_cast>(_STD forward<_Uty>(_Other)); } } diff --git a/stl/inc/optional b/stl/inc/optional index 2aae7cd4dd..28e41c46ab 100644 --- a/stl/inc/optional +++ b/stl/inc/optional @@ -252,7 +252,7 @@ public: negation, bool>, _Is_specialization<_Remove_cvref_t<_Ty2>, optional>>>, is_constructible<_Ty, _Ty2>>>; - template ::value, int> = 0> + template , enable_if_t<_AllowDirectConversion<_Ty2>::value, int> = 0> constexpr explicit(!is_convertible_v<_Ty2, _Ty>) optional(_Ty2&& _Right) noexcept(is_nothrow_constructible_v<_Ty, _Ty2>) // strengthened : _Mybase(in_place, _STD forward<_Ty2>(_Right)) {} @@ -288,10 +288,11 @@ public: return *this; } - template >>, - negation, is_same<_Ty, decay_t<_Ty2>>>>, - is_constructible<_Ty, _Ty2>, is_assignable<_Ty&, _Ty2>>, - int> = 0> + template , + enable_if_t>>, + negation, is_same<_Ty, decay_t<_Ty2>>>>, is_constructible<_Ty, _Ty2>, + is_assignable<_Ty&, _Ty2>>, + int> = 0> _CONSTEXPR20 optional& operator=(_Ty2&& _Right) noexcept(is_nothrow_assignable_v<_Ty&, _Ty2> && is_nothrow_constructible_v<_Ty, _Ty2>) /* strengthened */ { this->_Assign(_STD forward<_Ty2>(_Right)); @@ -425,13 +426,14 @@ public: return _STD move(this->_Value); } - template + template > _NODISCARD constexpr remove_cv_t<_Ty> value_or(_Ty2&& _Right) const& { static_assert(is_convertible_v>, "The const overload of optional::value_or requires const T& to be convertible to remove_cv_t " "(N4950 [optional.observe]/15 as modified by LWG-3424)."); - static_assert(is_convertible_v<_Ty2, _Ty>, - "optional::value_or(U) requires U to be convertible to T (N4950 [optional.observe]/15)."); + static_assert(is_convertible_v<_Ty2, remove_cv_t<_Ty>>, + "optional::value_or(U) requires U to be convertible to remove_cv_t " + "(N4950 [optional.observe]/15 as modified by LWG-3424)."); if (this->_Has_value) { return static_cast(this->_Value); @@ -439,13 +441,14 @@ public: return static_cast>(_STD forward<_Ty2>(_Right)); } - template + template > _NODISCARD constexpr remove_cv_t<_Ty> value_or(_Ty2&& _Right) && { static_assert(is_convertible_v<_Ty, remove_cv_t<_Ty>>, "The rvalue overload of optional::value_or requires T to be convertible to remove_cv_t " "(N4950 [optional.observe]/17 as modified by LWG-3424)."); - static_assert(is_convertible_v<_Ty2, _Ty>, - "optional::value_or(U) requires U to be convertible to T (N4950 [optional.observe]/17)."); + static_assert(is_convertible_v<_Ty2, remove_cv_t<_Ty>>, + "optional::value_or(U) requires U to be convertible to remove_cv_t " + "(N4950 [optional.observe]/17 as modified by LWG-3424)."); if (this->_Has_value) { return static_cast<_Ty&&>(this->_Value); diff --git a/tests/std/tests/P0220R1_optional/test.cpp b/tests/std/tests/P0220R1_optional/test.cpp index 34ed93ae66..af8fe44019 100644 --- a/tests/std/tests/P0220R1_optional/test.cpp +++ b/tests/std/tests/P0220R1_optional/test.cpp @@ -8372,6 +8372,11 @@ int run_test() #include #include +#if _HAS_CXX20 +#define CONSTEXPR20 constexpr +#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv +#define CONSTEXPR20 inline +#endif // ^^^ !_HAS_CXX20 ^^^ namespace msvc { namespace size { @@ -8422,12 +8427,6 @@ namespace msvc { namespace lwg3836 { static_assert(std::is_convertible_v, std::optional>); static_assert(std::is_convertible_v&, std::optional>); - -#if _HAS_CXX20 -#define CONSTEXPR20 constexpr -#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv -#define CONSTEXPR20 inline -#endif // ^^^ !_HAS_CXX20 ^^^ CONSTEXPR20 bool run_test() { std::optional oi = 0; std::optional ob = oi; @@ -8436,13 +8435,151 @@ namespace msvc { return true; } -#undef CONSTEXPR20 - #if _HAS_CXX20 static_assert(run_test()); #endif // _HAS_CXX20 } // namespace lwg3836 + namespace lwg3886 { + enum class Qualification { + None, + Const, + Volatile, + ConstVolatile, + }; + + template + constexpr Qualification CvQualOf = + std::is_const_v> + ? (std::is_volatile_v> ? Qualification::ConstVolatile : Qualification::Const) + : (std::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 >, QualDistinction>, int> = + 0> + 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 >, QualDistinction>, int> = + 0> + constexpr QualDistinction& operator=(T&&) noexcept { + qual_ = CvQualOf; + return *this; + } + template >, QualDistinction>, int> = + 0> + constexpr const QualDistinction& operator=(T&&) const noexcept { + qual_ = CvQualOf; + return *this; + } + template >, QualDistinction>, int> = + 0> + volatile QualDistinction& operator=(T&&) volatile noexcept { + qual_ = CvQualOf; + return *this; + } + template >, QualDistinction>, int> = + 0> + const volatile QualDistinction& operator=(T&&) const volatile noexcept { + qual_ = CvQualOf; + return *this; + } + + mutable Qualification qual_ = Qualification::None; + }; + + constexpr bool test_value_or() { + assert(std::optional{}.value_or({}).qual_ == Qualification::None); + assert(std::optional{}.value_or({}).qual_ == Qualification::None); + { + std::optional opt; + assert(opt.value_or({}).qual_ == Qualification::None); + } + { + std::optional opt; + assert(opt.value_or({}).qual_ == Qualification::None); + } + return true; + } + + CONSTEXPR20 bool test_assignment() { + assert((std::optional{} = {QualDistinction{}}).value().qual_ == Qualification::None); + assert((std::optional{} = {QualDistinction{}}).value().qual_ == Qualification::None); + { + std::optional opt{std::in_place}; + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + opt.reset(); + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + } + { + std::optional opt{std::in_place}; + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + opt.reset(); + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + } + return true; + } + + bool test_volatile() { + assert(std::optional{}.value_or({}).qual_ == Qualification::None); + assert(std::optional{}.value_or({}).qual_ == Qualification::None); + { + std::optional opt; + assert(opt.value_or({}).qual_ == Qualification::None); + } + { + std::optional opt; + assert(opt.value_or({}).qual_ == Qualification::None); + } + + assert( + (std::optional{} = {QualDistinction{}}).value().qual_ == Qualification::None); + assert((std::optional{} = {QualDistinction{}}).value().qual_ + == Qualification::None); + { + std::optional opt{std::in_place}; + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + opt.reset(); + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + } + { + std::optional opt{std::in_place}; + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + opt.reset(); + assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None); + } + + return true; + } + + static_assert(test_value_or()); +#if _HAS_CXX20 + static_assert(test_assignment()); +#endif // _HAS_CXX20 + + void run_test() { + test_value_or(); + test_assignment(); + test_volatile(); + } + } // namespace lwg3886 + namespace vso406124 { // Defend against regression of VSO-406124 void run_test() { @@ -8719,6 +8856,7 @@ int main() { optional_includes_initializer_list::run_test(); msvc::lwg3836::run_test(); + msvc::lwg3886::run_test(); msvc::vso406124::run_test(); msvc::vso508126::run_test(); diff --git a/tests/std/tests/P0323R12_expected/test.cpp b/tests/std/tests/P0323R12_expected/test.cpp index ba0e7f4d68..81335d3a8b 100644 --- a/tests/std/tests/P0323R12_expected/test.cpp +++ b/tests/std/tests/P0323R12_expected/test.cpp @@ -2316,6 +2316,133 @@ 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 = 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 = 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 = 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 = 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>); @@ -2389,6 +2516,8 @@ static_assert(!is_assignable_v&, ambiguating_expected_assign static_assert(!is_assignable_v&, ambiguating_expected_assignment_source>); static_assert(!is_assignable_v&, ambiguating_expected_assignment_source>); +static_assert(test_lwg_3886()); + int main() { test_unexpected::test_all(); static_assert(test_unexpected::test_all()); @@ -2405,5 +2534,7 @@ int main() { test_reinit_regression(); test_lwg_3843(); + test_lwg_3886(); + test_lwg_3886_volatile(); test_inherited_constructors(); }