]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Implement LWG 3836 for std::optional bool conversions
authorJonathan Wakely <jwakely@redhat.com>
Tue, 23 Jul 2024 11:45:37 +0000 (12:45 +0100)
committerJonathan Wakely <redi@gcc.gnu.org>
Thu, 25 Jul 2024 10:53:06 +0000 (11:53 +0100)
libstdc++-v3/ChangeLog:

* include/std/optional (optional): Constrain constructors to
prevent problematic bool conversions, as per LWG 3836.
* testsuite/20_util/optional/cons/lwg3836.cc: New test.

libstdc++-v3/include/std/optional
libstdc++-v3/testsuite/20_util/optional/cons/lwg3836.cc [new file with mode: 0644]

index 344be5e44d3ec54cfa812c3a637011366c5737bd..700e7047abae6efd0586fa7b0f68a3a853b9f6a1 100644 (file)
@@ -749,16 +749,17 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
   template<typename _Tp>
     inline constexpr bool __is_optional_v<optional<_Tp>> = true;
 
+  template<typename _Tp, typename _Wp>
+    using __converts_from_any_cvref = __or_<
+       is_constructible<_Tp, _Wp&>,       is_convertible<_Wp&, _Tp>,
+       is_constructible<_Tp, _Wp>,        is_convertible<_Wp, _Tp>,
+       is_constructible<_Tp, const _Wp&>, is_convertible<const _Wp&, _Tp>,
+       is_constructible<_Tp, const _Wp>,  is_convertible<const _Wp, _Tp>
+      >;
+
   template<typename _Tp, typename _Up>
-    using __converts_from_optional =
-      __or_<is_constructible<_Tp, const optional<_Up>&>,
-           is_constructible<_Tp, optional<_Up>&>,
-           is_constructible<_Tp, const optional<_Up>&&>,
-           is_constructible<_Tp, optional<_Up>&&>,
-           is_convertible<const optional<_Up>&, _Tp>,
-           is_convertible<optional<_Up>&, _Tp>,
-           is_convertible<const optional<_Up>&&, _Tp>,
-           is_convertible<optional<_Up>&&, _Tp>>;
+    using __converts_from_optional
+      = __converts_from_any_cvref<_Tp, optional<_Up>>;
 
   template<typename _Tp, typename _Up>
     using __assigns_from_optional =
@@ -800,6 +801,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename... _Cond>
        using _Requires = enable_if_t<__and_v<_Cond...>, bool>;
 
+      // _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
+      template<typename _From, typename = remove_cv_t<_Tp>>
+       struct __not_constructing_bool_from_optional
+       : true_type
+       { };
+
+      template<typename _From>
+       struct __not_constructing_bool_from_optional<_From, bool>
+       : bool_constant<!__is_optional_v<__remove_cvref_t<_From>>>
+       { };
+
+      template<typename _From, typename = remove_cv_t<_Tp>>
+       struct __construct_from_contained_value
+       : __not_<__converts_from_optional<_Tp, _From>>
+       { };
+
+      template<typename _From>
+       struct __construct_from_contained_value<_From, bool>
+       : true_type
+       { };
+
     public:
       using value_type = _Tp;
 
@@ -811,7 +836,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Up = _Tp,
               _Requires<__not_self<_Up>, __not_tag<_Up>,
                         is_constructible<_Tp, _Up>,
-                        is_convertible<_Up, _Tp>> = true>
+                        is_convertible<_Up, _Tp>,
+                        __not_constructing_bool_from_optional<_Up>> = true>
        constexpr
        optional(_Up&& __t)
        noexcept(is_nothrow_constructible_v<_Tp, _Up>)
@@ -820,7 +846,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       template<typename _Up = _Tp,
               _Requires<__not_self<_Up>, __not_tag<_Up>,
                         is_constructible<_Tp, _Up>,
-                        __not_<is_convertible<_Up, _Tp>>> = false>
+                        __not_<is_convertible<_Up, _Tp>>,
+                        __not_constructing_bool_from_optional<_Up>> = false>
        explicit constexpr
        optional(_Up&& __t)
        noexcept(is_nothrow_constructible_v<_Tp, _Up>)
@@ -830,7 +857,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               _Requires<__not_<is_same<_Tp, _Up>>,
                         is_constructible<_Tp, const _Up&>,
                         is_convertible<const _Up&, _Tp>,
-                        __not_<__converts_from_optional<_Tp, _Up>>> = true>
+                        __construct_from_contained_value<_Up>> = true>
        constexpr
        optional(const optional<_Up>& __t)
        noexcept(is_nothrow_constructible_v<_Tp, const _Up&>)
@@ -843,7 +870,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               _Requires<__not_<is_same<_Tp, _Up>>,
                         is_constructible<_Tp, const _Up&>,
                         __not_<is_convertible<const _Up&, _Tp>>,
-                        __not_<__converts_from_optional<_Tp, _Up>>> = false>
+                        __construct_from_contained_value<_Up>> = false>
        explicit constexpr
        optional(const optional<_Up>& __t)
        noexcept(is_nothrow_constructible_v<_Tp, const _Up&>)
@@ -856,7 +883,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               _Requires<__not_<is_same<_Tp, _Up>>,
                         is_constructible<_Tp, _Up>,
                         is_convertible<_Up, _Tp>,
-                        __not_<__converts_from_optional<_Tp, _Up>>> = true>
+                        __construct_from_contained_value<_Up>> = true>
        constexpr
        optional(optional<_Up>&& __t)
        noexcept(is_nothrow_constructible_v<_Tp, _Up>)
@@ -869,7 +896,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
               _Requires<__not_<is_same<_Tp, _Up>>,
                         is_constructible<_Tp, _Up>,
                         __not_<is_convertible<_Up, _Tp>>,
-                        __not_<__converts_from_optional<_Tp, _Up>>> = false>
+                        __construct_from_contained_value<_Up>> = false>
        explicit constexpr
        optional(optional<_Up>&& __t)
        noexcept(is_nothrow_constructible_v<_Tp, _Up>)
@@ -895,7 +922,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
                                            _Args...>)
        : _Base(std::in_place, __il, std::forward<_Args>(__args)...) { }
 
-
       // Assignment operators.
       _GLIBCXX20_CONSTEXPR optional&
       operator=(nullopt_t) noexcept
diff --git a/libstdc++-v3/testsuite/20_util/optional/cons/lwg3836.cc b/libstdc++-v3/testsuite/20_util/optional/cons/lwg3836.cc
new file mode 100644 (file)
index 0000000..816eb1b
--- /dev/null
@@ -0,0 +1,58 @@
+// { dg-do run { target c++17 } }
+
+#include <optional>
+#include <testsuite_hooks.h>
+
+constexpr void
+test_convert_contained_value_to_bool()
+{
+  struct False { constexpr operator bool() const { return false; } };
+
+  False f;
+  std::optional<False> o = f;
+
+  // Should use optional(const optional<U>&) ctor, not optional(U&&):
+  std::optional<bool> o2 = o;
+
+  // Contained value should be static_cast<bool>(f) not static_cast<bool>(o):
+  VERIFY( o2.value() == false );
+
+  std::optional<False> o3;
+  std::optional<const bool> o4 = o3;
+  // Should have no contained value, not static_cast<bool>(o3):
+  VERIFY( ! o4.has_value() );
+}
+
+constexpr void
+test_convert_contained_value_to_bool_explicit()
+{
+  struct False { constexpr explicit operator bool() const { return false; } };
+
+  False f;
+  std::optional<False> o = f;
+
+  // Should use optional(const optional<U>&) ctor, not optional(U&&):
+  std::optional<bool> o2(o);
+
+  // Contained value should be static_cast<bool>(f) not static_cast<bool>(o):
+  VERIFY( o2.value() == false );
+
+  std::optional<False> o3;
+  std::optional<const bool> o4(o3);
+  // Should have no contained value, not static_cast<bool>(o3):
+  VERIFY( ! o4.has_value() );
+}
+
+int main()
+{
+  test_convert_contained_value_to_bool();
+  test_convert_contained_value_to_bool_explicit();
+
+#if __cpp_lib_optional >= 202106
+  static_assert([] {
+    test_convert_contained_value_to_bool();
+    test_convert_contained_value_to_bool_explicit();
+    return true;
+  }());
+#endif
+}