[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