[libcxx-commits] [libcxx] [libc++] Fix bug where optional<T&> couldn't be constructed from a monadic operation (PR #203462)

William Tran-Viet via libcxx-commits libcxx-commits at lists.llvm.org
Thu Jun 11 23:46:41 PDT 2026


https://github.com/smallp-o-p updated https://github.com/llvm/llvm-project/pull/203462

>From 41fcdd24ba27c24a3e67897de8c81646a3701e78 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Fri, 12 Jun 2026 01:54:15 -0400
Subject: [PATCH 1/4] Fix bug where optional<T&> couldn't be constructed from a
 monadic operation

---
 libcxx/include/optional                       | 26 ++++++++++---
 .../optional.monadic/and_then.pass.cpp        | 37 ++++++++++++++++++-
 .../optional.monadic/or_else.pass.cpp         |  8 ++++
 .../optional.monadic/transform.pass.cpp       | 21 +++++++++--
 4 files changed, 81 insertions(+), 11 deletions(-)

diff --git a/libcxx/include/optional b/libcxx/include/optional
index 3a8191a3d1971..a98ddc7306480 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -571,6 +571,14 @@ struct __optional_storage_base<_Tp, true> {
     __convert_init_ref_val(std::forward<_UArg>(__uarg));
   }
 
+#    if _LIBCPP_STD_VER >= 23
+  template <class _Fp, class... _Args>
+  constexpr __optional_storage_base(__optional_construct_from_invoke_tag, _Fp&& __f, _Args&&... __args) {
+    _Tp& __r = std::invoke(std::forward<_Fp>(__f), std::forward<_Args>(__args)...);
+    __value_ = std::addressof(__r);
+  }
+#    endif
+
   _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 void reset() noexcept { __value_ = nullptr; }
 
   [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool has_value() const noexcept { return __value_ != nullptr; }
@@ -776,6 +784,14 @@ template <class _Tp, class _Up, class... _Args>
 inline constexpr bool __is_constructible_for_optional_initializer_list_v<_Tp&, _Up, _Args...> = false;
 #    endif
 
+#    if _LIBCPP_STD_VER >= 26
+template <class _Tp>
+inline constexpr bool __is_valid_optional_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;
+#    else
+template <class _Tp>
+inline constexpr bool __is_valid_optional_type = is_object_v<_Tp>;
+#    endif
+
 template <class _Tp, class = void>
 struct __optional_iterator_base : __optional_move_assign_base<_Tp> {
   using __optional_move_assign_base<_Tp>::__optional_move_assign_base;
@@ -1137,7 +1153,7 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
-    static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
     return optional<_Up>();
@@ -1149,7 +1165,7 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
-    static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
     return optional<_Up>();
@@ -1161,7 +1177,7 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
-    static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
     return optional<_Up>();
@@ -1173,7 +1189,7 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
-    static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
     return optional<_Up>();
@@ -1348,7 +1364,7 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
-    static_assert(is_object_v<_Up>, "Result of f(std::move(value())) should be an object type");
+    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
 
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
index 93bbd74cd8678..e806cd8dd5343 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/and_then.pass.cpp
@@ -261,8 +261,6 @@ constexpr bool test() {
 
 #if TEST_STD_VER >= 26
 constexpr bool test_ref() {
-  // Test "overloads", only the const (no ref-qualifier) and_then() should be called
-
   { // &
     // Without & qualifier on F's operator()
     {
@@ -339,6 +337,41 @@ constexpr bool test_ref() {
       assert(std::move(i).and_then(std::move(nl)) == std::nullopt);
     }
   }
+
+  {
+    int i = 1;
+    int j = 2;
+    {
+      std::optional<int&> o(i);
+      std::same_as<std::optional<int&>> decltype(auto) r = o.and_then([&](auto&& ii) {
+        ii += j;
+        return std::optional<int&>(ii);
+      });
+
+      assert(i == 3);
+      assert(r == 3);
+    }
+    {
+      const std::optional<int&> o(i);
+      std::same_as<std::optional<int&>> decltype(auto) r = o.and_then([&](auto&& ii) {
+        ii += j;
+        return std::optional<int&>(ii);
+      });
+
+      assert(i == 5);
+      assert(r == 5);
+    }
+    {
+      std::optional<int&> o{};
+      std::same_as<std::optional<int>> decltype(auto) r = o.and_then([&](auto&&) { return std::optional(1); });
+      assert(r == std::nullopt);
+    }
+    {
+      const std::optional<int&> o{};
+      std::same_as<std::optional<int>> decltype(auto) r = o.and_then([&](auto&&) { return std::optional(1); });
+      assert(r == std::nullopt);
+    }
+  }
   return true;
 }
 #endif
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
index 326013cd6557f..7252f0a8700df 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/or_else.pass.cpp
@@ -104,6 +104,14 @@ constexpr bool test() {
     }) == i);
   }
 
+  {
+    int j = 2;
+    std::optional<int&> opt;
+    std::same_as<std::optional<int&>> decltype(auto) o = opt.or_else([&] { return std::optional<int&>(j); });
+    assert(o == j);
+    assert(&(*o) == &j);
+  }
+
 #endif
 
   return true;
diff --git a/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp b/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
index b8536b790f749..96303c5796f6d 100644
--- a/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.monadic/transform.pass.cpp
@@ -204,8 +204,6 @@ constexpr bool test() {
 
 #if TEST_STD_VER >= 26
 constexpr bool test_ref() {
-  // Test that no matter the ref qualifier on the object .transform() is invoked on, only the added
-  // const (no ref-qualifier) overload is used
   {
     std::optional<int&> opt1;
     std::same_as<std::optional<int>> decltype(auto) opt1r = opt1.transform([](int i) { return i + 2; });
@@ -229,7 +227,6 @@ constexpr bool test_ref() {
     assert(*o2 == 4.0f);
   }
 
-  // &
   {
     // Without & qualifier on F's operator()
     {
@@ -250,7 +247,6 @@ constexpr bool test_ref() {
       assert(*o3 == 1);
     }
   }
-  // const& overload
   {
     // Without & qualifier on F's operator()
     {
@@ -308,6 +304,23 @@ constexpr bool test_ref() {
     auto o6r               = o6.transform([](int) { return 42; });
     assert(!o6r);
   }
+
+  {
+    int i = 42;
+    int j{43};
+
+    auto func = [&j](int&) -> int& { return j; };
+
+    std::optional<int&> opt{i};
+    std::same_as<std::optional<int&>> decltype(auto) o = opt.transform(func);
+    assert(o == j);
+    assert(&(*o) == &j);
+
+    std::same_as<std::optional<int&>> decltype(auto) o2 = std::as_const(opt).transform(func);
+    assert(o2 == j);
+    assert(&(*o2) == &j);
+  }
+
   return true;
 }
 #endif

>From 650036dc799c0699df5f8499423f126e77aef935 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Fri, 12 Jun 2026 02:44:23 -0400
Subject: [PATCH 2/4] Address defects

---
 libcxx/include/optional | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/libcxx/include/optional b/libcxx/include/optional
index a98ddc7306480..fa28a0e5f0317 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -574,8 +574,8 @@ struct __optional_storage_base<_Tp, true> {
 #    if _LIBCPP_STD_VER >= 23
   template <class _Fp, class... _Args>
   constexpr __optional_storage_base(__optional_construct_from_invoke_tag, _Fp&& __f, _Args&&... __args) {
-    _Tp& __r = std::invoke(std::forward<_Fp>(__f), std::forward<_Args>(__args)...);
-    __value_ = std::addressof(__r);
+    __convert_init_ref_val(std::forward<invoke_result_t<_Fp, _Args...>>(
+        std::invoke(std::forward<_Fp>(__f), std::forward<_Args>(__args)...)));
   }
 #    endif
 
@@ -784,12 +784,12 @@ template <class _Tp, class _Up, class... _Args>
 inline constexpr bool __is_constructible_for_optional_initializer_list_v<_Tp&, _Up, _Args...> = false;
 #    endif
 
-#    if _LIBCPP_STD_VER >= 26
+#    if _LIBCPP_STD_VER < 26
 template <class _Tp>
-inline constexpr bool __is_valid_optional_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;
+inline constexpr bool __is_valid_optional_type = is_object_v<_Tp>;
 #    else
 template <class _Tp>
-inline constexpr bool __is_valid_optional_type = is_object_v<_Tp>;
+inline constexpr bool __is_valid_optional_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;
 #    endif
 
 template <class _Tp, class = void>

>From f19bcefaef3bb1787c561616fd131b892794a5c1 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Fri, 12 Jun 2026 02:45:25 -0400
Subject: [PATCH 3/4] is valid optional type -> is valid optional contained
 type

---
 libcxx/include/optional | 17 +++++++++++------
 1 file changed, 11 insertions(+), 6 deletions(-)

diff --git a/libcxx/include/optional b/libcxx/include/optional
index fa28a0e5f0317..1adf09dd6731e 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -789,7 +789,7 @@ template <class _Tp>
 inline constexpr bool __is_valid_optional_type = is_object_v<_Tp>;
 #    else
 template <class _Tp>
-inline constexpr bool __is_valid_optional_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;
+inline constexpr bool __is_valid_optional_contained_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;
 #    endif
 
 template <class _Tp, class = void>
@@ -1153,7 +1153,8 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
-    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
+    static_assert(
+        __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
     return optional<_Up>();
@@ -1165,7 +1166,8 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
-    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
+    static_assert(
+        __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());
     return optional<_Up>();
@@ -1177,7 +1179,8 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
-    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
+    static_assert(
+        __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
     return optional<_Up>();
@@ -1189,7 +1192,8 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(std::move(value())) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(std::move(value())) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(std::move(value())) should not be std::nullopt_t");
-    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
+    static_assert(
+        __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), std::move(value()));
     return optional<_Up>();
@@ -1364,7 +1368,8 @@ public:
     static_assert(!is_array_v<_Up>, "Result of f(value()) should not be an Array");
     static_assert(!is_same_v<_Up, in_place_t>, "Result of f(value()) should not be std::in_place_t");
     static_assert(!is_same_v<_Up, nullopt_t>, "Result of f(value()) should not be std::nullopt_t");
-    static_assert(__is_valid_optional_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
+    static_assert(
+        __is_valid_optional_contained_type<_Up>, "Result of f(value()) should be a valid contained type for optional");
 
     if (*this)
       return optional<_Up>(__optional_construct_from_invoke_tag{}, std::forward<_Func>(__f), value());

>From 27520cf18406df70c81c158a1199e3a5c01bea75 Mon Sep 17 00:00:00 2001
From: William Tran-Viet <wtranviet at proton.me>
Date: Fri, 12 Jun 2026 02:46:31 -0400
Subject: [PATCH 4/4] Rename didn't propagte

---
 libcxx/include/optional | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libcxx/include/optional b/libcxx/include/optional
index 1adf09dd6731e..97e8b27e7f2d1 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -786,7 +786,7 @@ inline constexpr bool __is_constructible_for_optional_initializer_list_v<_Tp&, _
 
 #    if _LIBCPP_STD_VER < 26
 template <class _Tp>
-inline constexpr bool __is_valid_optional_type = is_object_v<_Tp>;
+inline constexpr bool __is_valid_optional_contained_type = is_object_v<_Tp>;
 #    else
 template <class _Tp>
 inline constexpr bool __is_valid_optional_contained_type = is_object_v<_Tp> || is_lvalue_reference_v<_Tp>;



More information about the libcxx-commits mailing list