diff mbox series

[1/2] libstdc++: Implement LWG 3836 for std::expected bool conversions

Message ID 20240724195358.4007988-1-jwakely@redhat.com
State New
Headers show
Series [1/2] libstdc++: Implement LWG 3836 for std::expected bool conversions | expand

Commit Message

Jonathan Wakely July 24, 2024, 7:53 p.m. UTC
Tested x86_64-linux.

-- >8 --

libstdc++-v3/ChangeLog:

	* include/std/expected (expected): Constrain constructors to
	prevent problematic bool conversions, as per LWG 3836.
	* testsuite/20_util/expected/lwg3836.cc: New test.
---
 libstdc++-v3/include/std/expected             | 59 ++++++++++++++-----
 .../testsuite/20_util/expected/lwg3836.cc     | 34 +++++++++++
 2 files changed, 77 insertions(+), 16 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/expected/lwg3836.cc
diff mbox series

Patch

diff --git a/libstdc++-v3/include/std/expected b/libstdc++-v3/include/std/expected
index 86026c3947a..2594cfe131c 100644
--- a/libstdc++-v3/include/std/expected
+++ b/libstdc++-v3/include/std/expected
@@ -314,6 +314,17 @@  namespace __expected
 	  __guard.release();
 	}
     }
+
+  // _GLIBCXX_RESOLVE_LIB_DEFECTS
+  // 3836. std::expected<bool, E1> conversion constructor
+  // expected(const expected<U, G>&) should take precedence over
+  // expected(U&&) with operator bool
+
+  // If T is cv bool, remove_cvref_t<U> is not a specialization of expected.
+  template<typename _Tp, typename _Up>
+    concept __not_constructing_bool_from_expected
+      = ! is_same_v<remove_cv_t<_Tp>, bool>
+	  || ! __is_expected<remove_cvref_t<_Up>>;
 }
 /// @endcond
 
@@ -327,26 +338,41 @@  namespace __expected
       static_assert( ! __expected::__is_unexpected<remove_cv_t<_Tp>> );
       static_assert( __expected::__can_be_unexpected<_Er> );
 
-      template<typename _Up, typename _Err, typename _Unex = unexpected<_Er>>
+      // If T is not cv bool, converts-from-any-cvref<T, expected<U, G>> and
+      // is_constructible<unexpected<E>, cv expected<U, G> ref-qual> are false.
+      template<typename _Up, typename _Gr, typename _Unex = unexpected<_Er>,
+	       typename = remove_cv_t<_Tp>>
 	static constexpr bool __cons_from_expected
-	  = __or_v<is_constructible<_Tp, expected<_Up, _Err>&>,
-		   is_constructible<_Tp, expected<_Up, _Err>>,
-		   is_constructible<_Tp, const expected<_Up, _Err>&>,
-		   is_constructible<_Tp, const expected<_Up, _Err>>,
-		   is_convertible<expected<_Up, _Err>&, _Tp>,
-		   is_convertible<expected<_Up, _Err>, _Tp>,
-		   is_convertible<const expected<_Up, _Err>&, _Tp>,
-		   is_convertible<const expected<_Up, _Err>, _Tp>,
-		   is_constructible<_Unex, expected<_Up, _Err>&>,
-		   is_constructible<_Unex, expected<_Up, _Err>>,
-		   is_constructible<_Unex, const expected<_Up, _Err>&>,
-		   is_constructible<_Unex, const expected<_Up, _Err>>
+	  = __or_v<is_constructible<_Tp, expected<_Up, _Gr>&>,
+		   is_constructible<_Tp, expected<_Up, _Gr>>,
+		   is_constructible<_Tp, const expected<_Up, _Gr>&>,
+		   is_constructible<_Tp, const expected<_Up, _Gr>>,
+		   is_convertible<expected<_Up, _Gr>&, _Tp>,
+		   is_convertible<expected<_Up, _Gr>, _Tp>,
+		   is_convertible<const expected<_Up, _Gr>&, _Tp>,
+		   is_convertible<const expected<_Up, _Gr>, _Tp>,
+		   is_constructible<_Unex, expected<_Up, _Gr>&>,
+		   is_constructible<_Unex, expected<_Up, _Gr>>,
+		   is_constructible<_Unex, const expected<_Up, _Gr>&>,
+		   is_constructible<_Unex, const expected<_Up, _Gr>>
 		  >;
 
-      template<typename _Up, typename _Err>
+      // _GLIBCXX_RESOLVE_LIB_DEFECTS
+      // If t is cv bool, we know it can be constructed from expected<U, G>,
+      // but we don't want to cause the expected(U&&) constructor to be used,
+      // so we only check the is_constructible<unexpected<E>, ...> cases.
+      template<typename _Up, typename _Gr, typename _Unex>
+	static constexpr bool __cons_from_expected<_Up, _Gr, _Unex, bool>
+	  = __or_v<is_constructible<_Unex, expected<_Up, _Gr>&>,
+		   is_constructible<_Unex, expected<_Up, _Gr>>,
+		   is_constructible<_Unex, const expected<_Up, _Gr>&>,
+		   is_constructible<_Unex, const expected<_Up, _Gr>>
+		  >;
+
+      template<typename _Up, typename _Gr>
 	constexpr static bool __explicit_conv
 	  = __or_v<__not_<is_convertible<_Up, _Tp>>,
-		   __not_<is_convertible<_Err, _Er>>
+		   __not_<is_convertible<_Gr, _Er>>
 		  >;
 
       template<typename _Up>
@@ -445,8 +471,9 @@  namespace __expected
       template<typename _Up = _Tp>
 	requires (!is_same_v<remove_cvref_t<_Up>, expected>)
 	  && (!is_same_v<remove_cvref_t<_Up>, in_place_t>)
-	  && (!__expected::__is_unexpected<remove_cvref_t<_Up>>)
 	  && is_constructible_v<_Tp, _Up>
+	  && (!__expected::__is_unexpected<remove_cvref_t<_Up>>)
+	  && __expected::__not_constructing_bool_from_expected<_Tp, _Up>
 	constexpr explicit(!is_convertible_v<_Up, _Tp>)
 	expected(_Up&& __v)
 	noexcept(is_nothrow_constructible_v<_Tp, _Up>)
diff --git a/libstdc++-v3/testsuite/20_util/expected/lwg3836.cc b/libstdc++-v3/testsuite/20_util/expected/lwg3836.cc
new file mode 100644
index 00000000000..cd029c44963
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/expected/lwg3836.cc
@@ -0,0 +1,34 @@ 
+// { dg-do run { target c++23 } }
+
+#include <expected>
+#include <testsuite_hooks.h>
+
+constexpr void
+test_convert_contained_value_to_bool()
+{
+  struct BaseError { };
+  struct DerivedError : BaseError { };
+
+  std::expected<bool, DerivedError> e = false;
+
+  // Should use expected(const expected<U, G>&) ctor, not expected(U&&):
+  std::expected<bool, BaseError> e2 = e;
+
+  // Contained value should be e.value() not static_cast<bool>(e):
+  VERIFY( e2.value() == false );
+
+  std::expected<bool, DerivedError> e3(std::unexpect);
+  std::expected<const bool, BaseError> e4 = e3;
+  // Should have error, not static_cast<bool>(e3):
+  VERIFY( ! e4.has_value() );
+}
+
+int main()
+{
+  test_convert_contained_value_to_bool();
+
+  static_assert([] {
+    test_convert_contained_value_to_bool();
+    return true;
+  }());
+}