[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