[libcxx-commits] [libcxx] [libc++] Strengthen optional value constructor noexcept (PR #202765)

Yuxuan Chen via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jun 9 13:45:57 PDT 2026


https://github.com/yuxuanchen1997 updated https://github.com/llvm/llvm-project/pull/202765

>From 30a521fb77173cf0df6fa5841ab723c33c82617e Mon Sep 17 00:00:00 2001
From: Yuxuan Chen <ych at meta.com>
Date: Tue, 9 Jun 2026 13:45:20 -0700
Subject: [PATCH] The standard does not require `optional<T>(U&&)` to be
 potentially throwing; it simply does not specify noexcept for the primary
 `optional<T>` converting constructor. Standard library implementations are
 permitted
 [[res.on.exception.handling]/5](https://eel.is/c++draft/res.on.exception.handling#5)
 to strengthen exception specifications for non-virtual library functions, as
 long as the strengthened specification is correct.

GNU libstdc++ already does this:
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional#L911-L913
https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/optional#L962-L974

The existing libc++ code only added noexcept for the C++26 `optional<T&>` case, guarded by `is_lvalue_reference_v<_Tp>`. It is safe to remove that gate and use the general condition instead:
```
noexcept(is_nothrow_constructible_v<_Tp, _Up>)
```

For `optional<T&>`, this still becomes the intended reference-construction check. For ordinary `optional<T>`, it correctly reflects whether constructing `T` from `U` can throw. The constructor only forwards into the contained object construction and updates optional bookkeeping, so if `T` is nothrow-constructible from `U`, the optional construction is also nothrow.
---
 libcxx/include/optional                       | 16 ++++----------
 .../optional.object.ctor/U.pass.cpp           | 21 +++++++++++++++++++
 2 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/libcxx/include/optional b/libcxx/include/optional
index d0ddd2c0b8ea2..bc00c11caebbb 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -984,21 +984,13 @@ public:
       : __base(in_place, __il, std::forward<_Args>(__args)...) {}
 
   template <class _Up = _Tp, enable_if_t<_CheckOptionalArgsCtor<_Up>::template __enable_implicit<_Up>(), int> = 0>
-  _LIBCPP_HIDE_FROM_ABI constexpr optional(_Up&& __v)
-#    if _LIBCPP_STD_VER >= 26
-      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up>)
-#    endif
-      : __base(in_place, std::forward<_Up>(__v)) {
-  }
+  _LIBCPP_HIDE_FROM_ABI constexpr optional(_Up&& __v) noexcept(is_nothrow_constructible_v<_Tp, _Up>)
+      : __base(in_place, std::forward<_Up>(__v)) {}
 
   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)
-#    if _LIBCPP_STD_VER >= 26
-      noexcept(is_lvalue_reference_v<_Tp> && is_nothrow_constructible_v<_Tp&, _Up>)
-#    endif
-      : __base(in_place, std::forward<_Up>(__v)) {
-  }
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit optional(_Up&& __v) noexcept(is_nothrow_constructible_v<_Tp, _Up>)
+      : __base(in_place, std::forward<_Up>(__v)) {}
 
   // LWG2756: conditionally explicit conversion from const optional<_Up>&
   template <class _Up, enable_if_t<_CheckOptionalLikeCtor<_Up, _Up const&>::template __enable_implicit<_Up>(), int> = 0>
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 1cdb429f485e5..274b62cedd77e 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
@@ -16,6 +16,7 @@
 #include <cassert>
 #include <optional>
 #include <type_traits>
+#include <vector>
 
 #include "test_macros.h"
 #include "archetypes.h"
@@ -44,6 +45,14 @@ struct ImplicitAny {
   constexpr ImplicitAny(U&&) {}
 };
 
+struct ImplicitNoThrow {
+  constexpr ImplicitNoThrow(int) noexcept {}
+};
+
+struct ExplicitNoThrow {
+  constexpr explicit ExplicitNoThrow(int) noexcept {}
+};
+
 template <class To, class From>
 constexpr bool implicit_conversion(optional<To>&& opt, const From& v) {
   using O = optional<To>;
@@ -98,6 +107,10 @@ void test_implicit() {
     static_assert(!test_convertible<O, std::in_place_t&&>(), "");
     static_assert(!test_convertible<O, const std::in_place_t&&>(), "");
   }
+  {
+    ASSERT_NOEXCEPT(optional<ImplicitNoThrow>(42));
+    ASSERT_NOT_NOEXCEPT(optional<ImplicitThrow>(42));
+  }
 #ifndef TEST_HAS_NO_EXCEPTIONS
   {
     try {
@@ -139,6 +152,14 @@ void test_explicit() {
     }
     assert(T::alive == 0);
   }
+  {
+    ASSERT_NOEXCEPT(optional<ExplicitNoThrow>(42));
+    ASSERT_NOT_NOEXCEPT(optional<ExplicitThrow>(42));
+  }
+  {
+    ASSERT_NOEXCEPT(optional<std::vector<int>>(std::declval<std::vector<int>&&>()));
+    static_assert(std::is_nothrow_constructible<optional<std::vector<int>>, std::vector<int>&&>::value, "");
+  }
 #ifndef TEST_HAS_NO_EXCEPTIONS
   {
     try {



More information about the libcxx-commits mailing list