[libcxx-commits] [libcxx] [libc++] Implement Resolution of LWG 3886 (PR #155356)
William Tran-Viet via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Aug 26 20:24:51 PDT 2025
https://github.com/smallp-o-p updated https://github.com/llvm/llvm-project/pull/155356
>From bcdfe307bd7f958c57994c842b3932335997577d Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Mon, 25 Aug 2025 23:56:27 -0400
Subject: [PATCH 1/5] Implement resolution of LWG 3886
---
libcxx/include/__expected/expected.h | 8 ++++----
libcxx/include/optional | 17 +++++++++--------
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/libcxx/include/__expected/expected.h b/libcxx/include/__expected/expected.h
index 38a34121040f6..8b3eeebd38ae7 100644
--- a/libcxx/include/__expected/expected.h
+++ b/libcxx/include/__expected/expected.h
@@ -555,7 +555,7 @@ class expected : private __expected_base<_Tp, _Err> {
is_nothrow_constructible_v<_Tp, _Up> && is_nothrow_constructible_v<_Err, _OtherErr>) // strengthened
: __base(__other.__has_val(), std::move(__other.__union())) {}
- template <class _Up = _Tp>
+ template <class _Up = remove_cv_t<_Tp>>
requires(!is_same_v<remove_cvref_t<_Up>, in_place_t> && !is_same_v<expected, remove_cvref_t<_Up>> &&
!is_same_v<remove_cvref_t<_Up>, unexpect_t> && is_constructible_v<_Tp, _Up> &&
!__is_std_unexpected<remove_cvref_t<_Up>>::value &&
@@ -669,7 +669,7 @@ class expected : private __expected_base<_Tp, _Err> {
return *this;
}
- template <class _Up = _Tp>
+ template <class _Up = remove_cv_t<_Tp>>
_LIBCPP_HIDE_FROM_ABI constexpr expected& operator=(_Up&& __v)
requires(!is_same_v<expected, remove_cvref_t<_Up>> && !__is_std_unexpected<remove_cvref_t<_Up>>::value &&
is_constructible_v<_Tp, _Up> && is_assignable_v<_Tp&, _Up> &&
@@ -887,14 +887,14 @@ class expected : private __expected_base<_Tp, _Err> {
return std::move(this->__unex());
}
- template <class _Up>
+ template <class _Up = remove_cv_t<_Tp>>
_LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) const& {
static_assert(is_copy_constructible_v<_Tp>, "value_type has to be copy constructible");
static_assert(is_convertible_v<_Up, _Tp>, "argument has to be convertible to value_type");
return this->__has_val() ? this->__val() : static_cast<_Tp>(std::forward<_Up>(__v));
}
- template <class _Up>
+ template <class _Up = remove_cv_t<_Tp>>
_LIBCPP_HIDE_FROM_ABI constexpr _Tp value_or(_Up&& __v) && {
static_assert(is_move_constructible_v<_Tp>, "value_type has to be move constructible");
static_assert(is_convertible_v<_Up, _Tp>, "argument has to be convertible to value_type");
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 39fcaa2c2ec18..ef1bfd3ec44c0 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -119,7 +119,7 @@ namespace std {
constexpr explicit optional(in_place_t, Args &&...);
template<class U, class... Args>
constexpr explicit optional(in_place_t, initializer_list<U>, Args &&...);
- template<class U = T>
+ template<class U = remove_cv_t<T>>
constexpr explicit(see-below) optional(U &&);
template<class U>
explicit(see-below) optional(const optional<U> &); // constexpr in C++20
@@ -133,7 +133,7 @@ namespace std {
optional &operator=(nullopt_t) noexcept; // constexpr in C++20
constexpr optional &operator=(const optional &);
constexpr optional &operator=(optional &&) noexcept(see below);
- template<class U = T> optional &operator=(U &&); // constexpr in C++20
+ template<class U = remove_cv_t<T>> optional &operator=(U &&); // constexpr in C++20
template<class U> optional &operator=(const optional<U> &); // constexpr in C++20
template<class U> optional &operator=(optional<U> &&); // constexpr in C++20
template<class... Args> T& emplace(Args &&...); // constexpr in C++20
@@ -161,8 +161,8 @@ namespace std {
constexpr T &value() &;
constexpr T &&value() &&;
constexpr const T &&value() const &&;
- template<class U> constexpr T value_or(U &&) const &;
- template<class U> constexpr T value_or(U &&) &&;
+ template<class U = remove_cv_t<T>> constexpr T value_or(U &&) const &;
+ template<class U = remove_cv_t<T>> constexpr T value_or(U &&) &&;
// [optional.monadic], monadic operations
template<class F> constexpr auto and_then(F&& f) &; // since C++23
@@ -730,7 +730,8 @@ public:
enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(), int> = 0>
_LIBCPP_HIDE_FROM_ABI constexpr optional(_Up&& __v) : __base(in_place, std::forward<_Up>(__v)) {}
- template <class _Up, enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_explicit<_Up>(), int> = 0>
+ template <class _Up = remove_cv_t<_Tp>,
+ enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_explicit<_Up>(), int> = 0>
_LIBCPP_HIDE_FROM_ABI constexpr explicit optional(_Up&& __v) : __base(in_place, std::forward<_Up>(__v)) {}
// LWG2756: conditionally explicit conversion from const optional<_Up>&
@@ -771,7 +772,7 @@ public:
_LIBCPP_HIDE_FROM_ABI constexpr optional& operator=(optional&&) = default;
// LWG2756
- template <class _Up = value_type,
+ template <class _Up = remove_cv_t<value_type>,
enable_if_t<_And<_IsNotSame<__remove_cvref_t<_Up>, optional>,
_Or<_IsNotSame<__remove_cvref_t<_Up>, value_type>, _Not<is_scalar<value_type>>>,
is_constructible<value_type, _Up>,
@@ -919,14 +920,14 @@ public:
return std::move(this->__get());
}
- template <class _Up>
+ template <class _Up = remove_cv_t<_Tp>>
_LIBCPP_HIDE_FROM_ABI constexpr value_type value_or(_Up&& __v) const& {
static_assert(is_copy_constructible_v<value_type>, "optional<T>::value_or: T must be copy constructible");
static_assert(is_convertible_v<_Up, value_type>, "optional<T>::value_or: U must be convertible to T");
return this->has_value() ? this->__get() : static_cast<value_type>(std::forward<_Up>(__v));
}
- template <class _Up>
+ template <class _Up = remove_cv_t<_Tp>>
_LIBCPP_HIDE_FROM_ABI constexpr value_type value_or(_Up&& __v) && {
static_assert(is_move_constructible_v<value_type>, "optional<T>::value_or: T must be move constructible");
static_assert(is_convertible_v<_Up, value_type>, "optional<T>::value_or: U must be convertible to T");
>From 288a9872ec053042cac942d0f9060a2861e4becb Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Tue, 26 Aug 2025 22:45:09 -0400
Subject: [PATCH 2/5] Add some extra tests
---
.../expected.expected/ctor/ctor.u.pass.cpp | 28 +++++++++++++++++++
.../optional.object.ctor/U.pass.cpp | 12 +++++++-
.../optional.object.observe/value_or.pass.cpp | 5 ++++
.../value_or_const.pass.cpp | 4 +++
4 files changed, 48 insertions(+), 1 deletion(-)
diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
index 13c0da27bc533..4c16176b6e394 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
@@ -80,6 +80,18 @@ struct CopyOnly {
friend constexpr bool operator==(const CopyOnly& mi, int ii) { return mi.i == ii; }
};
+struct MoveOnly2{
+ int j;
+ bool used_move1 = false;
+ bool used_move2 = false;
+
+ constexpr explicit MoveOnly2(int j) : j(j) {}
+ constexpr MoveOnly2(const MoveOnly2&) = delete;
+ constexpr MoveOnly2(MoveOnly2&& m) : j(m.j), used_move1(true) {}
+ constexpr MoveOnly2(const MoveOnly2&& m) : j(m.j), used_move2(true) {}
+
+};
+
struct BaseError {};
struct DerivedError : BaseError {};
@@ -164,6 +176,22 @@ constexpr bool test() {
assert(e2.has_value());
assert(!e2.value()); // yes, e2 holds "false" since LWG3836
}
+
+ // Check move constructor selection
+ {
+ MoveOnly2 t{1};
+ std::expected<MoveOnly2, BaseError> e1(std::move(t));
+ assert(e1.has_value());
+ assert(e1.value().used_move1 == true);
+ assert(e1.value().j == 1);
+ }
+ {
+ const MoveOnly2 t2{2};
+ std::expected<MoveOnly2, BaseError> e1(std::move(t2));
+ assert(e1.has_value());
+ assert(e1.value().used_move2 == true);
+ assert(e1.value().j == 2);
+ }
return true;
}
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp
index a5ee602ab7bce..edf9fa08d307a 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp
@@ -59,7 +59,8 @@ constexpr bool explicit_conversion(Input&& in, const Expect& v)
static_assert(!std::is_constructible<O, void*>::value, "");
static_assert(!std::is_constructible<O, Input, int>::value, "");
optional<To> opt(std::forward<Input>(in));
- return opt && *opt == static_cast<To>(v);
+ optional<To> opt2{std::forward<Input>(in)};
+ return opt && *opt == static_cast<To>(v) && (opt2 && *opt2 == static_cast<To>(v));
}
void test_implicit()
@@ -131,6 +132,15 @@ void test_explicit() {
assert(T::copy_constructed == 0);
assert(t.value().value == 42);
}
+ T::reset();
+ {
+ optional<T> t{43};
+ assert(T::alive == 1);
+ assert(T::value_constructed == 1);
+ assert(T::move_constructed == 0);
+ assert(T::copy_constructed == 0);
+ assert(t.value().value == 43);
+ }
assert(T::alive == 0);
}
#ifndef TEST_HAS_NO_EXCEPTIONS
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
index 4f9b6993c6f4f..deb65dd0a56d0 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
@@ -64,6 +64,11 @@ constexpr int test()
assert(std::move(opt).value_or(Y(3)) == 4);
assert(!opt);
}
+ {
+ optional<X> opt;
+ assert(std::move(opt).value_or<X>({Y(3)}) == 4);
+ assert(!opt);
+ }
return 0;
}
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
index cf782f1137876..355dae0ea498f 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
@@ -75,6 +75,10 @@ int main(int, char**)
const optional<X> opt;
assert(opt.value_or(Y(3)) == 4);
}
+ {
+ const optional<X> opt;
+ assert(opt.value_or<X>({Y(3)}) == 4);
+ }
return 0;
}
>From de1c135189923fbada590999bcb6cfbf460bf78d Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Tue, 26 Aug 2025 22:50:40 -0400
Subject: [PATCH 3/5] Formatting
---
.../expected/expected.expected/ctor/ctor.u.pass.cpp | 3 +--
.../optional.object.observe/value_or.pass.cpp | 6 +++---
.../optional.object.observe/value_or_const.pass.cpp | 4 ++--
3 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
index 4c16176b6e394..63f2fb5e947f8 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
@@ -80,7 +80,7 @@ struct CopyOnly {
friend constexpr bool operator==(const CopyOnly& mi, int ii) { return mi.i == ii; }
};
-struct MoveOnly2{
+struct MoveOnly2 {
int j;
bool used_move1 = false;
bool used_move2 = false;
@@ -89,7 +89,6 @@ struct MoveOnly2{
constexpr MoveOnly2(const MoveOnly2&) = delete;
constexpr MoveOnly2(MoveOnly2&& m) : j(m.j), used_move1(true) {}
constexpr MoveOnly2(const MoveOnly2&& m) : j(m.j), used_move2(true) {}
-
};
struct BaseError {};
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
index deb65dd0a56d0..f67643ef2e4cf 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
@@ -65,9 +65,9 @@ constexpr int test()
assert(!opt);
}
{
- optional<X> opt;
- assert(std::move(opt).value_or<X>({Y(3)}) == 4);
- assert(!opt);
+ optional<X> opt;
+ assert(std::move(opt).value_or<X>({Y(3)}) == 4);
+ assert(!opt);
}
return 0;
}
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
index 355dae0ea498f..5b3ff9131e6ce 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
@@ -76,8 +76,8 @@ int main(int, char**)
assert(opt.value_or(Y(3)) == 4);
}
{
- const optional<X> opt;
- assert(opt.value_or<X>({Y(3)}) == 4);
+ const optional<X> opt;
+ assert(opt.value_or<X>({Y(3)}) == 4);
}
return 0;
>From 915663c3c51d899876e26eb77c4c00e00a1eb873 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Tue, 26 Aug 2025 22:57:05 -0400
Subject: [PATCH 4/5] Deduce value_or template
---
.../optional.object/optional.object.observe/value_or.pass.cpp | 2 +-
.../optional.object.observe/value_or_const.pass.cpp | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
index f67643ef2e4cf..5f3ca4bbb0b6d 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or.pass.cpp
@@ -66,7 +66,7 @@ constexpr int test()
}
{
optional<X> opt;
- assert(std::move(opt).value_or<X>({Y(3)}) == 4);
+ assert(std::move(opt).value_or({Y(3)}) == 4);
assert(!opt);
}
return 0;
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
index 5b3ff9131e6ce..ec42890a3b995 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.observe/value_or_const.pass.cpp
@@ -77,7 +77,7 @@ int main(int, char**)
}
{
const optional<X> opt;
- assert(opt.value_or<X>({Y(3)}) == 4);
+ assert(opt.value_or({Y(3)}) == 4);
}
return 0;
>From 872a9aa149a4ad9ea320aa9a1ffb379ccdfb4802 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Tue, 26 Aug 2025 23:16:42 -0400
Subject: [PATCH 5/5] expect assign, formatting and fix shadow
---
.../assign/assign.U.pass.cpp | 35 +++++++++++++++++++
.../expected.expected/ctor/ctor.u.pass.cpp | 2 +-
.../optional.object.ctor/U.pass.cpp | 12 +++----
3 files changed, 42 insertions(+), 7 deletions(-)
diff --git a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
index 807a8af5bb5f6..0c0b0e3546006 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/assign/assign.U.pass.cpp
@@ -325,6 +325,41 @@ constexpr bool test() {
}
}
+ // Check move constructor selection
+ {
+ struct MoveOnlyMulti {
+ bool used_move1 = false;
+ bool used_move2 = false;
+
+ constexpr MoveOnlyMulti() = default;
+ constexpr MoveOnlyMulti(const MoveOnlyMulti&) = delete;
+ constexpr MoveOnlyMulti& operator=(const MoveOnlyMulti&) = delete;
+ constexpr MoveOnlyMulti& operator=(MoveOnlyMulti&&) {
+ used_move1 = true;
+ return *this;
+ }
+ constexpr MoveOnlyMulti& operator=(const MoveOnlyMulti&&) {
+ used_move2 = true;
+ return *this;
+ };
+ constexpr MoveOnlyMulti(MoveOnlyMulti&&) : used_move1(true) {}
+ constexpr MoveOnlyMulti(const MoveOnlyMulti&&) : used_move2(true) {}
+ };
+
+ {
+ MoveOnlyMulti t{};
+ std::expected<MoveOnlyMulti, int> e1(std::unexpect);
+ e1 = std::move(t);
+ assert(e1.value().used_move1);
+ }
+ {
+ const MoveOnlyMulti t{};
+ std::expected<MoveOnlyMulti, int> e1(std::unexpect);
+ e1 = std::move(t);
+ assert(e1.value().used_move2);
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
index 63f2fb5e947f8..fe664dfc97cfe 100644
--- a/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
+++ b/libcxx/test/std/utilities/expected/expected.expected/ctor/ctor.u.pass.cpp
@@ -85,7 +85,7 @@ struct MoveOnly2 {
bool used_move1 = false;
bool used_move2 = false;
- constexpr explicit MoveOnly2(int j) : j(j) {}
+ constexpr explicit MoveOnly2(int jj) : j(jj) {}
constexpr MoveOnly2(const MoveOnly2&) = delete;
constexpr MoveOnly2(MoveOnly2&& m) : j(m.j), used_move1(true) {}
constexpr MoveOnly2(const MoveOnly2&& m) : j(m.j), used_move2(true) {}
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp
index edf9fa08d307a..9d2badbf145b5 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/U.pass.cpp
@@ -134,12 +134,12 @@ void test_explicit() {
}
T::reset();
{
- optional<T> t{43};
- assert(T::alive == 1);
- assert(T::value_constructed == 1);
- assert(T::move_constructed == 0);
- assert(T::copy_constructed == 0);
- assert(t.value().value == 43);
+ optional<T> t{43};
+ assert(T::alive == 1);
+ assert(T::value_constructed == 1);
+ assert(T::move_constructed == 0);
+ assert(T::copy_constructed == 0);
+ assert(t.value().value == 43);
}
assert(T::alive == 0);
}
More information about the libcxx-commits
mailing list