Skip to content

Commit

Permalink
Implement LWG-3886 Monad mo' problems (in optional and expected) (m…
Browse files Browse the repository at this point in the history
…icrosoft#5232)

Co-authored-by: Stephan T. Lavavej <stl@microsoft.com>
  • Loading branch information
frederick-vs-ja and StephanTLavavej authored Jan 14, 2025
1 parent 9ec1309 commit 8578314
Show file tree
Hide file tree
Showing 4 changed files with 315 additions and 37 deletions.
42 changes: 24 additions & 18 deletions stl/inc/expected
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ public:
}
}

template <class _Uty = _Ty>
template <class _Uty = remove_cv_t<_Ty>>
requires (!is_same_v<remove_cvref_t<_Uty>, in_place_t> && !is_same_v<remove_cvref_t<_Uty>, expected>
&& !_Is_specialization_v<remove_cvref_t<_Uty>, unexpected>
&& (!is_same_v<remove_cv_t<_Ty>, bool>
Expand Down Expand Up @@ -469,7 +469,7 @@ public:
&& _Trivially_move_constructible_assignable_destructible<_Err>
= default;

template <class _Uty = _Ty>
template <class _Uty = remove_cv_t<_Ty>>
requires (!is_same_v<remove_cvref_t<_Uty>, expected> && !_Is_specialization_v<remove_cvref_t<_Uty>, unexpected>
&& is_constructible_v<_Ty, _Uty> && is_assignable_v<_Ty&, _Uty>
&& (is_nothrow_constructible_v<_Ty, _Uty> || is_nothrow_move_constructible_v<_Ty>
Expand Down Expand Up @@ -730,32 +730,38 @@ public:
return _STD move(_Unexpected);
}

template <class _Uty>
_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<T> must be true. (N4950 [expected.object.obs]/18)");
static_assert(
is_convertible_v<_Uty, _Ty>, "is_convertible_v<U, T> must be true. (N4950 [expected.object.obs]/18)");
template <class _Uty = remove_cv_t<_Ty>>
_NODISCARD constexpr remove_cv_t<_Ty> value_or(_Uty&& _Other) const& noexcept(
is_nothrow_convertible_v<const _Ty&, remove_cv_t<_Ty>>
&& is_nothrow_convertible_v<_Uty, remove_cv_t<_Ty>>) /* strengthened */ {
static_assert(is_convertible_v<const _Ty&, remove_cv_t<_Ty>>,
"is_convertible_v<const T&, remove_cv_t<T>> 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<U, remove_cv_t<T>> 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<remove_cv_t<_Ty>>(_STD forward<_Uty>(_Other));
}
}
template <class _Uty>
_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<T> must be true. (N4950 [expected.object.obs]/20)");
static_assert(
is_convertible_v<_Uty, _Ty>, "is_convertible_v<U, T> must be true. (N4950 [expected.object.obs]/20)");
template <class _Uty = remove_cv_t<_Ty>>
_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<T, remove_cv_t<T>> 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<U, remove_cv_t<T>> 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<remove_cv_t<_Ty>>(_STD forward<_Uty>(_Other));
}
}

Expand Down
25 changes: 14 additions & 11 deletions stl/inc/optional
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ public:
negation<conjunction<is_same<remove_cv_t<_Ty>, bool>, _Is_specialization<_Remove_cvref_t<_Ty2>, optional>>>,
is_constructible<_Ty, _Ty2>>>;

template <class _Ty2 = _Ty, enable_if_t<_AllowDirectConversion<_Ty2>::value, int> = 0>
template <class _Ty2 = remove_cv_t<_Ty>, 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)) {}
Expand Down Expand Up @@ -288,10 +288,11 @@ public:
return *this;
}

template <class _Ty2 = _Ty, enable_if_t<conjunction_v<negation<is_same<optional, _Remove_cvref_t<_Ty2>>>,
negation<conjunction<is_scalar<_Ty>, is_same<_Ty, decay_t<_Ty2>>>>,
is_constructible<_Ty, _Ty2>, is_assignable<_Ty&, _Ty2>>,
int> = 0>
template <class _Ty2 = remove_cv_t<_Ty>,
enable_if_t<conjunction_v<negation<is_same<optional, _Remove_cvref_t<_Ty2>>>,
negation<conjunction<is_scalar<_Ty>, 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));
Expand Down Expand Up @@ -425,27 +426,29 @@ public:
return _STD move(this->_Value);
}

template <class _Ty2>
template <class _Ty2 = remove_cv_t<_Ty>>
_NODISCARD constexpr remove_cv_t<_Ty> value_or(_Ty2&& _Right) const& {
static_assert(is_convertible_v<const _Ty&, remove_cv_t<_Ty>>,
"The const overload of optional<T>::value_or requires const T& to be convertible to remove_cv_t<T> "
"(N4950 [optional.observe]/15 as modified by LWG-3424).");
static_assert(is_convertible_v<_Ty2, _Ty>,
"optional<T>::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<T>::value_or(U) requires U to be convertible to remove_cv_t<T> "
"(N4950 [optional.observe]/15 as modified by LWG-3424).");

if (this->_Has_value) {
return static_cast<const _Ty&>(this->_Value);
}

return static_cast<remove_cv_t<_Ty>>(_STD forward<_Ty2>(_Right));
}
template <class _Ty2>
template <class _Ty2 = remove_cv_t<_Ty>>
_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<T>::value_or requires T to be convertible to remove_cv_t<T> "
"(N4950 [optional.observe]/17 as modified by LWG-3424).");
static_assert(is_convertible_v<_Ty2, _Ty>,
"optional<T>::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<T>::value_or(U) requires U to be convertible to remove_cv_t<T> "
"(N4950 [optional.observe]/17 as modified by LWG-3424).");

if (this->_Has_value) {
return static_cast<_Ty&&>(this->_Value);
Expand Down
154 changes: 146 additions & 8 deletions tests/std/tests/P0220R1_optional/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8372,6 +8372,11 @@ int run_test()
#include <type_traits>
#include <utility>

#if _HAS_CXX20
#define CONSTEXPR20 constexpr
#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv
#define CONSTEXPR20 inline
#endif // ^^^ !_HAS_CXX20 ^^^

namespace msvc {
namespace size {
Expand Down Expand Up @@ -8422,12 +8427,6 @@ namespace msvc {
namespace lwg3836 {
static_assert(std::is_convertible_v<std::optional<int>, std::optional<bool>>);
static_assert(std::is_convertible_v<const std::optional<int>&, std::optional<bool>>);

#if _HAS_CXX20
#define CONSTEXPR20 constexpr
#else // ^^^ _HAS_CXX20 / !_HAS_CXX20 vvv
#define CONSTEXPR20 inline
#endif // ^^^ !_HAS_CXX20 ^^^
CONSTEXPR20 bool run_test() {
std::optional<int> oi = 0;
std::optional<bool> ob = oi;
Expand All @@ -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 <class T>
constexpr Qualification CvQualOf =
std::is_const_v<std::remove_reference_t<T>>
? (std::is_volatile_v<std::remove_reference_t<T>> ? Qualification::ConstVolatile : Qualification::Const)
: (std::is_volatile_v<std::remove_reference_t<T>> ? Qualification::Volatile : Qualification::None);

struct QualDistinction {
QualDistinction() = default;

constexpr QualDistinction(QualDistinction&&) noexcept : qual_{Qualification::None} {}
constexpr QualDistinction(const QualDistinction&) noexcept : qual_{Qualification::Const} {}
template <class T,
std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, QualDistinction>, int> =
0>
constexpr QualDistinction(T&&) noexcept : qual_{CvQualOf<T>} {}

constexpr QualDistinction& operator=(QualDistinction&&) noexcept {
qual_ = Qualification::None;
return *this;
}
constexpr QualDistinction& operator=(const QualDistinction&) noexcept {
qual_ = Qualification::Const;
return *this;
}
template <class T,
std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, QualDistinction>, int> =
0>
constexpr QualDistinction& operator=(T&&) noexcept {
qual_ = CvQualOf<T>;
return *this;
}
template <class T,
std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, QualDistinction>, int> =
0>
constexpr const QualDistinction& operator=(T&&) const noexcept {
qual_ = CvQualOf<T>;
return *this;
}
template <class T,
std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, QualDistinction>, int> =
0>
volatile QualDistinction& operator=(T&&) volatile noexcept {
qual_ = CvQualOf<T>;
return *this;
}
template <class T,
std::enable_if_t<std::is_same_v<std::remove_cv_t<std::remove_reference_t<T>>, QualDistinction>, int> =
0>
const volatile QualDistinction& operator=(T&&) const volatile noexcept {
qual_ = CvQualOf<T>;
return *this;
}

mutable Qualification qual_ = Qualification::None;
};

constexpr bool test_value_or() {
assert(std::optional<QualDistinction>{}.value_or({}).qual_ == Qualification::None);
assert(std::optional<const QualDistinction>{}.value_or({}).qual_ == Qualification::None);
{
std::optional<QualDistinction> opt;
assert(opt.value_or({}).qual_ == Qualification::None);
}
{
std::optional<const QualDistinction> opt;
assert(opt.value_or({}).qual_ == Qualification::None);
}
return true;
}

CONSTEXPR20 bool test_assignment() {
assert((std::optional<QualDistinction>{} = {QualDistinction{}}).value().qual_ == Qualification::None);
assert((std::optional<const QualDistinction>{} = {QualDistinction{}}).value().qual_ == Qualification::None);
{
std::optional<QualDistinction> opt{std::in_place};
assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None);
opt.reset();
assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None);
}
{
std::optional<const QualDistinction> 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<volatile QualDistinction>{}.value_or({}).qual_ == Qualification::None);
assert(std::optional<const volatile QualDistinction>{}.value_or({}).qual_ == Qualification::None);
{
std::optional<volatile QualDistinction> opt;
assert(opt.value_or({}).qual_ == Qualification::None);
}
{
std::optional<const volatile QualDistinction> opt;
assert(opt.value_or({}).qual_ == Qualification::None);
}

assert(
(std::optional<volatile QualDistinction>{} = {QualDistinction{}}).value().qual_ == Qualification::None);
assert((std::optional<const volatile QualDistinction>{} = {QualDistinction{}}).value().qual_
== Qualification::None);
{
std::optional<volatile QualDistinction> opt{std::in_place};
assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None);
opt.reset();
assert((opt = {QualDistinction{}}).value().qual_ == Qualification::None);
}
{
std::optional<const volatile QualDistinction> 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() {
Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit 8578314

Please sign in to comment.