diff mbox series

libstdc++: Implement LWG 3886 for std::optional and std::expected

Message ID 20240730133337.1025999-1-jwakely@redhat.com
State New
Headers show
Series libstdc++: Implement LWG 3886 for std::optional and std::expected | expand

Commit Message

Jonathan Wakely July 30, 2024, 1:32 p.m. UTC
This LWG issue is about to become Tentatively Ready.

Tested x86_64-linux.

-- >8 --

This uses remove_cv_t<T> for the default template argument used for
deducing a type for a braced-init-list used with std::optional and
std::expected.

libstdc++-v3/ChangeLog:

	* include/std/expected (expected(U&&), operator=(U&&))
	(value_or): Use remove_cv_t on default template argument, as per
	LWG 3886.
	* include/std/optional (optional(U&&), operator=(U&&))
	(value_or): Likewise.
	* testsuite/20_util/expected/lwg3886.cc: New test.
	* testsuite/20_util/optional/cons/lwg3886.cc: New test.
---
 libstdc++-v3/include/std/expected             |  8 +--
 libstdc++-v3/include/std/optional             | 12 ++--
 .../testsuite/20_util/expected/lwg3886.cc     | 58 +++++++++++++++++++
 .../20_util/optional/cons/lwg3886.cc          | 58 +++++++++++++++++++
 4 files changed, 126 insertions(+), 10 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/expected/lwg3886.cc
 create mode 100644 libstdc++-v3/testsuite/20_util/optional/cons/lwg3886.cc
diff mbox series

Patch

diff --git a/libstdc++-v3/include/std/expected b/libstdc++-v3/include/std/expected
index 515a1e6ab8f..b8217e577fa 100644
--- a/libstdc++-v3/include/std/expected
+++ b/libstdc++-v3/include/std/expected
@@ -468,7 +468,7 @@  namespace __expected
 			      std::move(__x)._M_unex);
 	}
 
-      template<typename _Up = _Tp>
+      template<typename _Up = remove_cv_t<_Tp>>
 	requires (!is_same_v<remove_cvref_t<_Up>, expected>)
 	  && (!is_same_v<remove_cvref_t<_Up>, in_place_t>)
 	  && is_constructible_v<_Tp, _Up>
@@ -582,7 +582,7 @@  namespace __expected
 	return *this;
       }
 
-      template<typename _Up = _Tp>
+      template<typename _Up = remove_cv_t<_Tp>>
 	requires (!is_same_v<expected, remove_cvref_t<_Up>>)
 	      && (!__expected::__is_unexpected<remove_cvref_t<_Up>>)
 	      && is_constructible_v<_Tp, _Up> && is_assignable_v<_Tp&, _Up>
@@ -818,7 +818,7 @@  namespace __expected
 	return std::move(_M_unex);
       }
 
-      template<typename _Up>
+      template<typename _Up = remove_cv_t<_Tp>>
 	constexpr _Tp
 	value_or(_Up&& __v) const &
 	noexcept(__and_v<is_nothrow_copy_constructible<_Tp>,
@@ -832,7 +832,7 @@  namespace __expected
 	  return static_cast<_Tp>(std::forward<_Up>(__v));
 	}
 
-      template<typename _Up>
+      template<typename _Up = remove_cv_t<_Tp>>
 	constexpr _Tp
 	value_or(_Up&& __v) &&
 	noexcept(__and_v<is_nothrow_move_constructible<_Tp>,
diff --git a/libstdc++-v3/include/std/optional b/libstdc++-v3/include/std/optional
index 4694d594f98..2c4cc260f90 100644
--- a/libstdc++-v3/include/std/optional
+++ b/libstdc++-v3/include/std/optional
@@ -868,7 +868,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       // Converting constructors for engaged optionals.
 #ifdef _GLIBCXX_USE_CONSTRAINTS_FOR_OPTIONAL
-      template<typename _Up = _Tp>
+      template<typename _Up = remove_cv_t<_Tp>>
 	requires (!is_same_v<optional, remove_cvref_t<_Up>>)
 	  && (!is_same_v<in_place_t, remove_cvref_t<_Up>>)
 	  && is_constructible_v<_Tp, _Up>
@@ -919,7 +919,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	: _Base(std::in_place, __il, std::forward<_Args>(__args)...)
 	{ }
 #else
-      template<typename _Up = _Tp,
+      template<typename _Up = remove_cv_t<_Tp>,
 	       _Requires<__not_self<_Up>, __not_tag<_Up>,
 			 is_constructible<_Tp, _Up>,
 			 is_convertible<_Up, _Tp>,
@@ -929,7 +929,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	noexcept(is_nothrow_constructible_v<_Tp, _Up>)
 	: _Base(std::in_place, std::forward<_Up>(__t)) { }
 
-      template<typename _Up = _Tp,
+      template<typename _Up = remove_cv_t<_Tp>,
 	       _Requires<__not_self<_Up>, __not_tag<_Up>,
 			 is_constructible<_Tp, _Up>,
 			 __not_<is_convertible<_Up, _Tp>>,
@@ -1017,7 +1017,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	return *this;
       }
 
-      template<typename _Up = _Tp>
+      template<typename _Up = remove_cv_t<_Tp>>
 #ifdef _GLIBCXX_USE_CONSTRAINTS_FOR_OPTIONAL
 	requires (!is_same_v<optional, remove_cvref_t<_Up>>)
 	  && (!(is_scalar_v<_Tp> && is_same_v<_Tp, decay_t<_Up>>))
@@ -1242,7 +1242,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	__throw_bad_optional_access();
       }
 
-      template<typename _Up>
+      template<typename _Up = remove_cv_t<_Tp>>
 	constexpr _Tp
 	value_or(_Up&& __u) const&
 	{
@@ -1255,7 +1255,7 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	    return static_cast<_Tp>(std::forward<_Up>(__u));
 	}
 
-      template<typename _Up>
+      template<typename _Up = remove_cv_t<_Tp>>
 	constexpr _Tp
 	value_or(_Up&& __u) &&
 	{
diff --git a/libstdc++-v3/testsuite/20_util/expected/lwg3886.cc b/libstdc++-v3/testsuite/20_util/expected/lwg3886.cc
new file mode 100644
index 00000000000..cf1a2ce4421
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/expected/lwg3886.cc
@@ -0,0 +1,58 @@ 
+// { dg-do compile { target c++23 } }
+
+// LWG 3886. Monad mo' problems
+
+#include <expected>
+
+void
+test_constructor()
+{
+  struct MoveOnly {
+    MoveOnly(int, int) { }
+    MoveOnly(MoveOnly&&) { }
+  };
+
+  // The {0,0} should be deduced as MoveOnly not const MoveOnly
+  [[maybe_unused]] std::expected<const MoveOnly, int> e({0,0});
+}
+
+struct Tracker {
+  bool moved = false;
+  constexpr Tracker(int, int) { }
+  constexpr Tracker(const Tracker&) { }
+  constexpr Tracker(Tracker&&) : moved(true) { }
+
+  // The follow means that is_assignable<const Tracker&, U> is true:
+  template<typename T> constexpr void operator=(T&&) const { }
+  // This stops a copy assignment from being declared implicitly:
+  void operator=(Tracker&) = delete;
+};
+
+void
+test_assignment()
+{
+  constexpr bool moved = [] {
+    std::expected<const Tracker, int> e(std::unexpect);
+    // The {0,0} should be deduced as Tracker not const Tracker:
+    e = {0,0};
+    // So the contained value should have been move constructed not copied:
+    return e->moved;
+  }();
+  static_assert( moved );
+}
+
+void
+test_value_or()
+{
+  constexpr bool moved = [] {
+    const std::expected<const Tracker, int> e(std::unexpect, 1);
+    return e.value_or({0,0}).moved;
+  }();
+  static_assert( moved );
+
+  constexpr bool moved_rval = [] {
+    std::expected<const Tracker, int> e(std::unexpect, 1);
+    return std::move(e).value_or({0,0}).moved;
+  }();
+  static_assert( moved_rval );
+}
diff --git a/libstdc++-v3/testsuite/20_util/optional/cons/lwg3886.cc b/libstdc++-v3/testsuite/20_util/optional/cons/lwg3886.cc
new file mode 100644
index 00000000000..f6323ca7cbc
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/optional/cons/lwg3886.cc
@@ -0,0 +1,58 @@ 
+// { dg-do compile { target c++17 } }
+
+// LWG 3886. Monad mo' problems
+
+#include <optional>
+
+void
+test_cons()
+{
+  struct MoveOnly {
+    MoveOnly(int, int) { }
+    MoveOnly(MoveOnly&&) { }
+  };
+
+  // The {0,0} should be deduced as MoveOnly not const MoveOnly
+  [[maybe_unused]] std::optional<const MoveOnly> o({0,0});
+}
+
+struct Tracker {
+  bool moved = false;
+  constexpr Tracker(int, int) { }
+  constexpr Tracker(const Tracker&) { }
+  constexpr Tracker(Tracker&&) : moved(true) { }
+
+  // The follow means that is_assignable<const Tracker&, U> is true:
+  template<typename T> constexpr void operator=(T&&) const { }
+};
+
+#if __cpp_lib_optional >= 202106L // for constexpr assignment
+void
+test_assignment()
+{
+  constexpr bool moved = [] {
+    std::optional<const Tracker> o;
+    // The {0,0} should be deduced as Tracker not const Tracker:
+    o = {0,0};
+    // So the contained value should have been move constructed not copied:
+    return o->moved;
+  }();
+  static_assert( moved );
+}
+#endif
+
+void
+test_value_or()
+{
+  constexpr bool moved = [] {
+    std::optional<const Tracker> o;
+    return o.value_or({0,0}).moved;
+  }();
+  static_assert( moved );
+
+  constexpr bool moved_rval = [] {
+    std::optional<const Tracker> o;
+    return std::move(o).value_or({0,0}).moved;
+  }();
+  static_assert( moved_rval );
+}