[libcxx-commits] [libcxx] [libc++] Fix constraints for `optional`'s constructors taking `in_place_t` and related `make_optional` overloads (PR #173467)
A. Jiang via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Dec 24 00:31:07 PST 2025
https://github.com/frederick-vs-ja updated https://github.com/llvm/llvm-project/pull/173467
>From acda83a228a97b8473483edd10ceefa8a67e6761 Mon Sep 17 00:00:00 2001
From: "A. Jiang" <de34 at live.cn>
Date: Wed, 24 Dec 2025 16:03:17 +0800
Subject: [PATCH] [libc++] Fix constraints for `optional`'s constructors taking
`in_place_t` and related `make_optional` overloads
Some constraints are incorrect for constructors of `optional<T&>`:
- for the `(in_place_t, Arg&&)` constructor, it should be more
constrained to reject dangling references;
- for the `(in_place_t, initializer_list<U>, Args&&...)` constructor, it
shouldn't be available for `optional<T&>` at all.
For `make_optional` overloads, the standard wording already required
them to propagate SFINAE constraints before LWG3627 (via "_Effects_:
Equivalent to", see also [structure.specifications]/4). So they need to
be constrained.
Drive-by: Refactor test files to run more cases during constant
evaluation.
---
libcxx/include/optional | 40 ++++++-
.../optional.object.ctor/in_place_t.pass.cpp | 33 +++++-
.../make_optional_explicit.pass.cpp | 104 +++++++++++++-----
...ptional_explicit_initializer_list.pass.cpp | 59 +++++++---
4 files changed, 187 insertions(+), 49 deletions(-)
diff --git a/libcxx/include/optional b/libcxx/include/optional
index 440fdf73a4310..e5a99c5f35b99 100644
--- a/libcxx/include/optional
+++ b/libcxx/include/optional
@@ -284,6 +284,7 @@ namespace std {
# include <__type_traits/decay.h>
# include <__type_traits/disjunction.h>
# include <__type_traits/enable_if.h>
+# include <__type_traits/integral_constant.h>
# include <__type_traits/invoke.h>
# include <__type_traits/is_array.h>
# include <__type_traits/is_assignable.h>
@@ -748,6 +749,27 @@ public:
# endif
};
+template <class _Tp, class... _Args>
+inline constexpr bool __is_constructible_for_optional_v = is_constructible_v<_Tp, _Args...>;
+
+template <class _Tp, class... _Args>
+struct __is_constructible_for_optional : bool_constant<__is_constructible_for_optional_v<_Tp, _Args...>> {};
+
+template <class _Tp, class _Up, class... _Args>
+inline constexpr bool __is_constructible_for_optional_initializer_list_v =
+ is_constructible_v<_Tp, initializer_list<_Up>&, _Args...>;
+
+# if _LIBCPP_STD_VER >= 26
+template <class _Tp, class... _Args>
+inline constexpr bool __is_constructible_for_optional_v<_Tp&, _Args...> = false;
+template <class _Tp, class _Arg>
+inline constexpr bool __is_constructible_for_optional_v<_Tp&, _Arg> =
+ is_constructible_v<_Tp&, _Arg> && !reference_constructs_from_temporary_v<_Tp&, _Arg>;
+
+template <class _Tp, class _Up, class... _Args>
+inline constexpr bool __is_constructible_for_optional_initializer_list_v<_Tp&, _Up, _Args...> = false;
+# endif
+
template <class _Tp>
class _LIBCPP_DECLSPEC_EMPTY_BASES optional
: private __optional_move_assign_base<_Tp>,
@@ -851,13 +873,16 @@ public:
_LIBCPP_HIDE_FROM_ABI constexpr optional(optional&&) = default;
_LIBCPP_HIDE_FROM_ABI constexpr optional(nullopt_t) noexcept {}
- template <class _InPlaceT,
- class... _Args,
- enable_if_t<_And<_IsSame<_InPlaceT, in_place_t>, is_constructible<_Tp, _Args...>>::value, int> = 0>
+ template <
+ class _InPlaceT,
+ class... _Args,
+ enable_if_t<_And<_IsSame<_InPlaceT, in_place_t>, __is_constructible_for_optional<_Tp, _Args...>>::value, int> = 0>
_LIBCPP_HIDE_FROM_ABI constexpr explicit optional(_InPlaceT, _Args&&... __args)
: __base(in_place, std::forward<_Args>(__args)...) {}
- template <class _Up, class... _Args, enable_if_t<is_constructible_v<_Tp, initializer_list<_Up>&, _Args...>, int> = 0>
+ template <class _Up,
+ class... _Args,
+ enable_if_t<__is_constructible_for_optional_initializer_list_v<_Tp, _Up, _Args...>, int> = 0>
_LIBCPP_HIDE_FROM_ABI constexpr explicit optional(in_place_t, initializer_list<_Up> __il, _Args&&... __args)
: __base(in_place, __il, std::forward<_Args>(__args)...) {}
@@ -1511,12 +1536,15 @@ template <
return optional<decay_t<_Tp>>(std::forward<_Tp>(__v));
}
-template <class _Tp, class... _Args>
+template <class _Tp, class... _Args, enable_if_t<__is_constructible_for_optional_v<_Tp, _Args...>, int> = 0>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional<_Tp> make_optional(_Args&&... __args) {
return optional<_Tp>(in_place, std::forward<_Args>(__args)...);
}
-template <class _Tp, class _Up, class... _Args>
+template <class _Tp,
+ class _Up,
+ class... _Args,
+ enable_if_t<__is_constructible_for_optional_initializer_list_v<_Tp, _Up, _Args...>, int> = 0>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr optional<_Tp>
make_optional(initializer_list<_Up> __il, _Args&&... __args) {
return optional<_Tp>(in_place, __il, std::forward<_Args>(__args)...);
diff --git a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/in_place_t.pass.cpp b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/in_place_t.pass.cpp
index 902754418fbde..c8bdfc78225fa 100644
--- a/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/in_place_t.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.object/optional.object.ctor/in_place_t.pass.cpp
@@ -11,7 +11,9 @@
// <optional>
// template <class... Args>
-// constexpr explicit optional(in_place_t, Args&&... args);
+// constexpr explicit optional(in_place_t, Args&&... args); // only for the primary template
+// template <class Arg>
+// constexpr explicit optional(in_place_t, Arg arg); // since C++26, only for optional<T&>
#include <cassert>
#include <optional>
@@ -54,6 +56,30 @@ class Z {
Z(int) { TEST_THROW(6); }
};
+#if TEST_STD_VER >= 26
+static_assert(!std::is_constructible_v<optional<const int&>, in_place_t>);
+static_assert(std::is_constructible_v<optional<const int&>, in_place_t, int&>);
+static_assert(std::is_constructible_v<optional<const int&>, in_place_t, const int&>);
+static_assert(!std::is_constructible_v<optional<const int&>, in_place_t, int>);
+static_assert(!std::is_constructible_v<optional<const int&>, in_place_t, const int>);
+static_assert(!std::is_constructible_v<optional<const int&>, in_place_t, long&>);
+static_assert(!std::is_constructible_v<optional<const int&>, in_place_t, const long&>);
+
+// Test that initilization in std::optional<std::initializer_list<T>&>{in_place, il} selects the (in_place_t, Arg&&)
+// constructor.
+// Otherwise, the created optional would store a dangling reference.
+constexpr bool test_ref_initializer_list() {
+ std::initializer_list<int> il{4, 2};
+ optional<std::initializer_list<int>&> opt{in_place, il};
+
+ auto il2 = opt.value();
+ assert(il2.begin() == il.begin());
+ assert(il2.size() == il.size());
+
+ return true;
+}
+#endif
+
int main(int, char**) {
{
constexpr optional<int> opt(in_place, 5);
@@ -121,5 +147,10 @@ int main(int, char**) {
}
#endif
+#if TEST_STD_VER >= 26
+ test_ref_initializer_list();
+ static_assert(test_ref_initializer_list());
+#endif
+
return 0;
}
diff --git a/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit.pass.cpp b/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit.pass.cpp
index b08fce2b701e2..cf30ca1152fb8 100644
--- a/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit.pass.cpp
@@ -16,53 +16,99 @@
#include <memory>
#include <optional>
#include <string>
-#include <string_view>
+#include <type_traits>
+#include <utility>
#include "test_macros.h"
+template <class T, class U>
+struct mandate_same {
+ ASSERT_SAME_TYPE(T, U);
+ static constexpr bool value = true;
+};
+
+template <class V, class T, class... Us>
+constexpr bool can_make_optional_explicit_impl = false;
+template <class T, class... Us>
+constexpr bool
+ can_make_optional_explicit_impl<std::void_t<decltype(std::make_optional<T>(std::declval<Us>()...))>, T, Us...> =
+ mandate_same<decltype(std::make_optional<T>(std::declval<Us>()...)), std::optional<T>>::value;
+
+template <class T, class... Us>
+constexpr bool can_make_optional_explicit = can_make_optional_explicit_impl<void, T, Us...>;
+
+static_assert(can_make_optional_explicit<int, long>);
+static_assert(can_make_optional_explicit<int, const long&>);
+static_assert(can_make_optional_explicit<std::string, const char*>);
+static_assert(can_make_optional_explicit<std::string, const char* const&>);
+static_assert(can_make_optional_explicit<std::unique_ptr<const int>, std::unique_ptr<int>>);
+static_assert(!can_make_optional_explicit<std::unique_ptr<const int>, const std::unique_ptr<int>>);
+static_assert(!can_make_optional_explicit<std::unique_ptr<const int>, std::unique_ptr<int>&>);
+static_assert(!can_make_optional_explicit<std::unique_ptr<const int>, const std::unique_ptr<int>&>);
+
+#if TEST_STD_VER >= 26
+static_assert(can_make_optional_explicit<const int&, int&>);
+static_assert(can_make_optional_explicit<const int&, const int&>);
+static_assert(!can_make_optional_explicit<const int&, int>);
+static_assert(!can_make_optional_explicit<const int&, const int>);
+static_assert(!can_make_optional_explicit<const int&, long&>);
+static_assert(!can_make_optional_explicit<const int&, const long&>);
+static_assert(!can_make_optional_explicit<const int&, long>);
+static_assert(!can_make_optional_explicit<const int&, const long&>);
+#endif
+
template <typename T>
-constexpr bool test_ref() {
+constexpr void test_ref() {
T i{0};
auto opt = std::make_optional<T&>(i);
#if TEST_STD_VER < 26
- assert((std::is_same_v<decltype(opt), std::optional<T>>));
+ ASSERT_SAME_TYPE(decltype(opt), std::optional<T>);
#else
- assert((std::is_same_v<decltype(opt), std::optional<T&>>));
+ ASSERT_SAME_TYPE(decltype(opt), std::optional<T&>);
#endif
assert(*opt == 0);
-
- return true;
}
-int main(int, char**)
-{
- {
- constexpr auto opt = std::make_optional<int>('a');
- static_assert(*opt == int('a'));
- }
- {
- std::string s = "123";
- auto opt = std::make_optional<std::string>(s);
- assert(*opt == "123");
- }
+constexpr bool test() {
+ {
+ constexpr auto opt = std::make_optional<int>('a');
+ static_assert(*opt == int('a'));
+ }
+
+ constexpr auto test_string = [] {
{
- std::unique_ptr<int> s = std::make_unique<int>(3);
- auto opt = std::make_optional<std::unique_ptr<int>>(std::move(s));
- assert(**opt == 3);
- assert(s == nullptr);
+ std::string s = "123";
+ auto opt = std::make_optional<std::string>(s);
+ assert(*opt == "123");
}
{
- auto opt = std::make_optional<std::string>(4u, 'X');
- assert(*opt == "XXXX");
+ auto opt = std::make_optional<std::string>(4u, 'X');
+ assert(*opt == "XXXX");
}
- using namespace std::string_view_literals;
+ };
+ if (TEST_STD_AT_LEAST_20_OR_RUNTIME_EVALUATED)
+ test_string();
+
+ constexpr auto test_unique_ptr = [] {
+ std::unique_ptr<int> s = std::make_unique<int>(3);
+ auto opt = std::make_optional<std::unique_ptr<int>>(std::move(s));
+ assert(**opt == 3);
+ assert(s == nullptr);
+ };
+ if (TEST_STD_AT_LEAST_23_OR_RUNTIME_EVALUATED)
+ test_unique_ptr();
+
+ test_ref<int>();
+ test_ref<double>();
+
+ return true;
+}
- static_assert(test_ref<int>());
- assert((test_ref<int>()));
- static_assert(test_ref<double>());
- assert((test_ref<double>()));
+int main(int, char**) {
+ test();
+ static_assert(test());
- return 0;
+ return 0;
}
diff --git a/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit_initializer_list.pass.cpp b/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit_initializer_list.pass.cpp
index 80371d6333712..cba168c92f3ec 100644
--- a/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit_initializer_list.pass.cpp
+++ b/libcxx/test/std/utilities/optional/optional.specalg/make_optional_explicit_initializer_list.pass.cpp
@@ -16,9 +16,37 @@
#include <memory>
#include <optional>
#include <string>
+#include <type_traits>
+#include <utility>
#include "test_macros.h"
+template <class T, class U>
+struct mandate_same {
+ ASSERT_SAME_TYPE(T, U);
+ static constexpr bool value = true;
+};
+
+template <class V, class T, class... Us>
+constexpr bool can_make_optional_explicit_impl = false;
+template <class T, class... Us>
+constexpr bool
+ can_make_optional_explicit_impl<std::void_t<decltype(std::make_optional<T>(std::declval<Us>()...))>, T, Us...> =
+ mandate_same<decltype(std::make_optional<T>(std::declval<Us>()...)), std::optional<T>>::value;
+
+template <class T, class... Us>
+constexpr bool can_make_optional_explicit = can_make_optional_explicit_impl<void, T, Us...>;
+
+static_assert(!can_make_optional_explicit<int, std::initializer_list<int>&>);
+static_assert(can_make_optional_explicit<std::string, std::initializer_list<char>&>);
+static_assert(can_make_optional_explicit<std::string, std::initializer_list<char>&, std::allocator<char>>);
+static_assert(!can_make_optional_explicit<std::string, std::initializer_list<std::string>&>);
+
+#if TEST_STD_VER >= 26
+static_assert(!can_make_optional_explicit<const int&, std::initializer_list<int>&>);
+static_assert(can_make_optional_explicit<std::initializer_list<int>&, std::initializer_list<int>&>);
+#endif
+
struct TestT {
int x;
int size;
@@ -29,8 +57,7 @@ struct TestT {
: x(*il.begin()), size(static_cast<int>(il.size())), ptr(p) {}
};
-constexpr bool test()
-{
+constexpr bool test() {
{
auto opt = std::make_optional<TestT>({42, 2, 3});
ASSERT_SAME_TYPE(decltype(opt), std::optional<TestT>);
@@ -39,27 +66,33 @@ constexpr bool test()
assert(opt->ptr == nullptr);
}
{
- int i = 42;
+ int i = 42;
auto opt = std::make_optional<TestT>({42, 2, 3}, &i);
ASSERT_SAME_TYPE(decltype(opt), std::optional<TestT>);
assert(opt->x == 42);
assert(opt->size == 3);
assert(opt->ptr == &i);
}
+
+ constexpr auto test_string = [] {
+ {
+ auto opt = std::make_optional<std::string>({'1', '2', '3'});
+ assert(*opt == "123");
+ }
+ {
+ auto opt = std::make_optional<std::string>({'a', 'b', 'c'}, std::allocator<char>{});
+ assert(*opt == "abc");
+ }
+ };
+ if (TEST_STD_AT_LEAST_20_OR_RUNTIME_EVALUATED)
+ test_string();
+
return true;
}
-int main(int, char**)
-{
+int main(int, char**) {
test();
static_assert(test());
- {
- auto opt = std::make_optional<std::string>({'1', '2', '3'});
- assert(*opt == "123");
- }
- {
- auto opt = std::make_optional<std::string>({'a', 'b', 'c'}, std::allocator<char>{});
- assert(*opt == "abc");
- }
+
return 0;
}
More information about the libcxx-commits
mailing list