[libcxx-commits] [libcxx] [libc++] Implement LWG3436: support for arrays in std::construct_at (PR #132283)

via libcxx-commits libcxx-commits at lists.llvm.org
Thu Mar 20 13:58:46 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-libcxx

Author: Louis Dionne (ldionne)

<details>
<summary>Changes</summary>

Fixes #<!-- -->118335

---
Full diff: https://github.com/llvm/llvm-project/pull/132283.diff


7 Files Affected:

- (modified) libcxx/docs/Status/Cxx2cIssues.csv (+1-1) 
- (modified) libcxx/include/__memory/construct_at.h (+15-3) 
- (modified) libcxx/include/__memory/ranges_construct_at.h (+5-1) 
- (added) libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp (+23) 
- (modified) libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp (+78-7) 
- (added) libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp (+23) 
- (modified) libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp (+83-19) 


``````````diff
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
index 9bf31c417f3c9..3f1e7fef4520a 100644
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -77,7 +77,7 @@
 "`LWG4106 <https://wg21.link/LWG4106>`__","``basic_format_args`` should not be default-constructible","2024-06 (St. Louis)","|Complete|","19",""
 "","","","","",""
 "`LWG3216 <https://wg21.link/LWG3216>`__","Rebinding the allocator before calling ``construct``/``destroy`` in ``allocate_shared``","2024-11 (Wrocław)","","",""
-"`LWG3436 <https://wg21.link/LWG3436>`__","``std::construct_at`` should support arrays","2024-11 (Wrocław)","","",""
+"`LWG3436 <https://wg21.link/LWG3436>`__","``std::construct_at`` should support arrays","2024-11 (Wrocław)","|Complete|","21",""
 "`LWG3886 <https://wg21.link/LWG3886>`__","Monad mo' problems","2024-11 (Wrocław)","","",""
 "`LWG3899 <https://wg21.link/LWG3899>`__","``co_yield``\ing elements of an lvalue generator is unnecessarily inefficient","2024-11 (Wrocław)","","",""
 "`LWG3900 <https://wg21.link/LWG3900>`__","The ``allocator_arg_t`` overloads of ``generator::promise_type::operator new`` should not be constrained","2024-11 (Wrocław)","","",""
diff --git a/libcxx/include/__memory/construct_at.h b/libcxx/include/__memory/construct_at.h
index 21337e766b2d0..f6f49d6daa0e6 100644
--- a/libcxx/include/__memory/construct_at.h
+++ b/libcxx/include/__memory/construct_at.h
@@ -17,6 +17,7 @@
 #include <__new/placement_new_delete.h>
 #include <__type_traits/enable_if.h>
 #include <__type_traits/is_array.h>
+#include <__type_traits/is_unbounded_array.h>
 #include <__utility/declval.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
@@ -34,15 +35,26 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 
 #if _LIBCPP_STD_VER >= 20
 
-template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))>
+template <class _Tp,
+          class... _Args,
+          class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)),
+          __enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0>
 _LIBCPP_HIDE_FROM_ABI constexpr _Tp* construct_at(_Tp* __location, _Args&&... __args) {
   _LIBCPP_ASSERT_NON_NULL(__location != nullptr, "null pointer given to construct_at");
-  return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...);
+  if constexpr (is_array_v<_Tp>) {
+    static_assert(sizeof...(_Args) == 0, "construction arguments cannot be passed to construct_at with an array type");
+    return ::new (static_cast<void*>(__location)) _Tp[1]();
+  } else {
+    return ::new (static_cast<void*>(__location)) _Tp(std::forward<_Args>(__args)...);
+  }
 }
 
 #endif
 
-template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))>
+template <class _Tp,
+          class... _Args,
+          class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)),
+          __enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _Tp* __construct_at(_Tp* __location, _Args&&... __args) {
 #if _LIBCPP_STD_VER >= 20
   return std::construct_at(__location, std::forward<_Args>(__args)...);
diff --git a/libcxx/include/__memory/ranges_construct_at.h b/libcxx/include/__memory/ranges_construct_at.h
index b8a94678c10c8..3d98262ec3650 100644
--- a/libcxx/include/__memory/ranges_construct_at.h
+++ b/libcxx/include/__memory/ranges_construct_at.h
@@ -19,6 +19,7 @@
 #include <__ranges/access.h>
 #include <__ranges/concepts.h>
 #include <__ranges/dangling.h>
+#include <__type_traits/is_unbounded_array.h>
 #include <__utility/declval.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
@@ -38,7 +39,10 @@ namespace ranges {
 // construct_at
 
 struct __construct_at {
-  template <class _Tp, class... _Args, class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...))>
+  template <class _Tp,
+            class... _Args,
+            class = decltype(::new(std::declval<void*>()) _Tp(std::declval<_Args>()...)),
+            __enable_if_t<!is_unbounded_array_v<_Tp>, int> = 0>
   _LIBCPP_HIDE_FROM_ABI constexpr _Tp* operator()(_Tp* __location, _Args&&... __args) const {
     return std::construct_at(__location, std::forward<_Args>(__args)...);
   }
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp
new file mode 100644
index 0000000000000..e732b75c4dcde
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.array.verify.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: stdlib=libc++
+
+// <memory>
+
+// Test that std::construct_at provides a meaningful diagnostic when used with an
+// array type and construction arguments are provided. See LWG3436.
+
+#include <memory>
+
+using Array = int[3];
+void test(Array* a) {
+  std::construct_at(a, 1, 2, 3);
+  // expected-error-re@*:* {{static assertion failed {{.*}}construction arguments cannot be passed to construct_at with an array type}}
+}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
index 00ec287ffeb66..10e3c7fbeecdd 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
@@ -14,11 +14,13 @@
 // constexpr T* construct_at(T* location, Args&& ...args);
 
 #include <cassert>
+#include <concepts>
 #include <cstddef>
 #include <memory>
 #include <utility>
 
 #include "test_iterators.h"
+#include "test_macros.h"
 
 struct Foo {
   constexpr Foo() {}
@@ -39,25 +41,31 @@ struct Counted {
   constexpr ~Counted() { --count_; }
 };
 
+struct CountDefaultInitializations {
+  CountDefaultInitializations() { ++constructions; }
+  static int constructions;
+};
+int CountDefaultInitializations::constructions = 0;
+
 constexpr bool test() {
   {
-    int i    = 99;
-    int* res = std::construct_at(&i);
+    int i                       = 99;
+    std::same_as<int*> auto res = std::construct_at(&i);
     assert(res == &i);
     assert(*res == 0);
   }
 
   {
-    int i    = 0;
-    int* res = std::construct_at(&i, 42);
+    int i                       = 0;
+    std::same_as<int*> auto res = std::construct_at(&i, 42);
     assert(res == &i);
     assert(*res == 42);
   }
 
   {
-    Foo foo   = {};
-    int count = 0;
-    Foo* res  = std::construct_at(&foo, 42, 'x', 123.89, &count);
+    Foo foo                     = {};
+    int count                   = 0;
+    std::same_as<Foo*> auto res = std::construct_at(&foo, 42, 'x', 123.89, &count);
     assert(res == &foo);
     assert(*res == Foo(42, 'x', 123.89));
     assert(count == 1);
@@ -78,12 +86,70 @@ constexpr bool test() {
     a.deallocate(p, 2);
   }
 
+  // Test LWG3436, std::construct_at with array types
+  {
+    {
+      using Array = int[1];
+      Array array;
+      std::same_as<Array*> auto result = std::construct_at(&array);
+      assert(result == &array);
+      assert(array[0] == 0);
+    }
+    {
+      using Array = int[2];
+      Array array;
+      std::same_as<Array*> auto result = std::construct_at(&array);
+      assert(result == &array);
+      assert(array[0] == 0);
+      assert(array[1] == 0);
+    }
+    {
+      using Array = int[3];
+      Array array;
+      std::same_as<Array*> auto result = std::construct_at(&array);
+      assert(result == &array);
+      assert(array[0] == 0);
+      assert(array[1] == 0);
+      assert(array[2] == 0);
+    }
+
+    // Make sure we initialize the right number of elements. This can't be done inside
+    // constexpr since it requires a global variable.
+    if (!TEST_IS_CONSTANT_EVALUATED) {
+      {
+        using Array = CountDefaultInitializations[1];
+        CountDefaultInitializations array[1];
+        CountDefaultInitializations::constructions = 0;
+        std::construct_at(&array);
+        assert(CountDefaultInitializations::constructions == 1);
+      }
+      {
+        using Array = CountDefaultInitializations[2];
+        CountDefaultInitializations array[2];
+        CountDefaultInitializations::constructions = 0;
+        std::construct_at(&array);
+        assert(CountDefaultInitializations::constructions == 2);
+      }
+      {
+        using Array = CountDefaultInitializations[3];
+        CountDefaultInitializations array[3];
+        CountDefaultInitializations::constructions = 0;
+        std::construct_at(&array);
+        assert(CountDefaultInitializations::constructions == 3);
+      }
+    }
+  }
+
   return true;
 }
 
 template <class... Args>
 constexpr bool can_construct_at = requires { std::construct_at(std::declval<Args>()...); };
 
+struct NoDefault {
+  NoDefault() = delete;
+};
+
 // Check that SFINAE works.
 static_assert(can_construct_at<int*, int>);
 static_assert(can_construct_at<Foo*, int, char, double>);
@@ -96,6 +162,11 @@ static_assert(!can_construct_at<contiguous_iterator<Foo*>, int, char, double>);
 static_assert(!can_construct_at<int (*)()>);
 static_assert(!can_construct_at<int (*)(), std::nullptr_t>);
 
+// LWG3436
+static_assert(can_construct_at<int (*)[3]>);        // test the test
+static_assert(!can_construct_at<int (*)[]>);        // unbounded arrays should SFINAE away
+static_assert(!can_construct_at<NoDefault (*)[1]>); // non default constructible shouldn't work
+
 int main(int, char**) {
   test();
   static_assert(test());
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp
new file mode 100644
index 0000000000000..1a1b8db96c8b4
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.array.verify.cpp
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: stdlib=libc++
+
+// <memory>
+
+// Test that std::ranges::construct_at provides a meaningful diagnostic when used with an
+// array type and construction arguments are provided. See LWG3436.
+
+#include <memory>
+
+using Array = int[3];
+void test(Array* a) {
+  std::ranges::construct_at(a, 1, 2, 3);
+  // expected-error-re@*:* {{static assertion failed {{.*}}construction arguments cannot be passed to construct_at with an array type}}
+}
diff --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
index 66e6f87432703..91e0597b6a6c6 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
@@ -16,6 +16,7 @@
 // }
 
 #include <cassert>
+#include <concepts>
 #include <initializer_list>
 #include <memory>
 #include <type_traits>
@@ -52,12 +53,18 @@ struct Counted {
   constexpr ~Counted() { --count; }
 };
 
+struct CountDefaultInitializations {
+  CountDefaultInitializations() { ++constructions; }
+  static int constructions;
+};
+int CountDefaultInitializations::constructions = 0;
+
 constexpr bool test() {
   // Value initialization.
   {
     int x = 1;
 
-    int* result = std::ranges::construct_at(&x);
+    std::same_as<int*> auto result = std::ranges::construct_at(&x);
     assert(result == &x);
     assert(x == 0);
   }
@@ -66,7 +73,7 @@ constexpr bool test() {
   {
     int x = 1;
 
-    int* result = std::ranges::construct_at(&x, 42);
+    std::same_as<int*> auto result = std::ranges::construct_at(&x, 42);
     assert(result == &x);
     assert(x == 42);
   }
@@ -75,7 +82,7 @@ constexpr bool test() {
   {
     Foo f;
 
-    Foo* result = std::ranges::construct_at(std::addressof(f), 42, 123);
+    std::same_as<Foo*> auto result = std::ranges::construct_at(std::addressof(f), 42, 123);
     assert(result == std::addressof(f));
     assert(f.x == 42);
     assert(f.y == 123);
@@ -87,7 +94,7 @@ constexpr bool test() {
     Counted* out = alloc.allocate(2);
     int count    = 0;
 
-    Counted* result = std::ranges::construct_at(out, count);
+    std::same_as<Counted*> auto result = std::ranges::construct_at(out, count);
     assert(result == out);
     assert(count == 1);
 
@@ -99,28 +106,85 @@ constexpr bool test() {
     alloc.deallocate(out, 2);
   }
 
-  return true;
-}
+  // Test LWG3436, std::ranges::construct_at with array types
+  {
+    {
+      using Array = int[1];
+      Array array;
+      std::same_as<Array*> auto result = std::ranges::construct_at(&array);
+      assert(result == &array);
+      assert(array[0] == 0);
+    }
+    {
+      using Array = int[2];
+      Array array;
+      std::same_as<Array*> auto result = std::ranges::construct_at(&array);
+      assert(result == &array);
+      assert(array[0] == 0);
+      assert(array[1] == 0);
+    }
+    {
+      using Array = int[3];
+      Array array;
+      std::same_as<Array*> auto result = std::ranges::construct_at(&array);
+      assert(result == &array);
+      assert(array[0] == 0);
+      assert(array[1] == 0);
+      assert(array[2] == 0);
+    }
+
+    // Make sure we initialize the right number of elements. This can't be done inside
+    // constexpr since it requires a global variable.
+    if (!TEST_IS_CONSTANT_EVALUATED) {
+      {
+        using Array = CountDefaultInitializations[1];
+        CountDefaultInitializations array[1];
+        CountDefaultInitializations::constructions = 0;
+        std::ranges::construct_at(&array);
+        assert(CountDefaultInitializations::constructions == 1);
+      }
+      {
+        using Array = CountDefaultInitializations[2];
+        CountDefaultInitializations array[2];
+        CountDefaultInitializations::constructions = 0;
+        std::ranges::construct_at(&array);
+        assert(CountDefaultInitializations::constructions == 2);
+      }
+      {
+        using Array = CountDefaultInitializations[3];
+        CountDefaultInitializations array[3];
+        CountDefaultInitializations::constructions = 0;
+        std::ranges::construct_at(&array);
+        assert(CountDefaultInitializations::constructions == 3);
+      }
+    }
+  }
 
-constexpr bool can_construct_at(auto&&... args)
-  requires requires { std::ranges::construct_at(decltype(args)(args)...); }
-{
   return true;
 }
 
-constexpr bool can_construct_at(auto&&...) { return false; }
+template <class... Args>
+constexpr bool can_construct_at = requires { std::ranges::construct_at(std::declval<Args>()...); };
+
+struct NoDefault {
+  NoDefault() = delete;
+};
 
 // Check that SFINAE works.
-static_assert(can_construct_at((Foo*)nullptr, 1, 2));
-static_assert(!can_construct_at((Foo*)nullptr, 1));
-static_assert(!can_construct_at((Foo*)nullptr, 1, 2, 3));
-static_assert(!can_construct_at(nullptr, 1, 2));
-static_assert(!can_construct_at((int*)nullptr, 1, 2));
-static_assert(!can_construct_at(contiguous_iterator<Foo*>(), 1, 2));
+static_assert(can_construct_at<Foo*, int, int>);
+static_assert(!can_construct_at<Foo*, int>);
+static_assert(!can_construct_at<Foo*, int, int, int>);
+static_assert(!can_construct_at<std::nullptr_t, int, int>);
+static_assert(!can_construct_at<int*, int, int>);
+static_assert(!can_construct_at<contiguous_iterator<Foo*>, int, int>);
 // Can't construct function pointers.
-static_assert(!can_construct_at((int (*)()) nullptr));
-static_assert(!can_construct_at((int (*)()) nullptr, nullptr));
-// TODO(varconst): check that array types work once D114649 implementing LWG3639 lands.
+static_assert(!can_construct_at<int (*)()>);
+static_assert(!can_construct_at<int (*)(), std::nullptr_t>);
+
+// LWG3436
+static_assert(can_construct_at<int (*)[3]>);        // test the test
+static_assert(!can_construct_at<int (*)[]>);        // unbounded arrays should SFINAE away
+static_assert(!can_construct_at<NoDefault (*)[1]>); // non default constructible shouldn't work
 
 int main(int, char**) {
   test();

``````````

</details>


https://github.com/llvm/llvm-project/pull/132283


More information about the libcxx-commits mailing list