[libcxx-commits] [libcxx] b9bc3c1 - [libc++][ranges] Implement `construct_at` and `destroy{, _at}`.

Konstantin Varlamov via libcxx-commits libcxx-commits at lists.llvm.org
Wed Jan 12 16:01:47 PST 2022


Author: Konstantin Varlamov
Date: 2022-01-12T16:01:04-08:00
New Revision: b9bc3c107c6cee93cd1a7004142f11741e0225bf

URL: https://github.com/llvm/llvm-project/commit/b9bc3c107c6cee93cd1a7004142f11741e0225bf
DIFF: https://github.com/llvm/llvm-project/commit/b9bc3c107c6cee93cd1a7004142f11741e0225bf.diff

LOG: [libc++][ranges] Implement `construct_at` and `destroy{,_at}`.

Differential Revision: https://reviews.llvm.org/D116078

Added: 
    libcxx/include/__memory/ranges_construct_at.h
    libcxx/test/libcxx/diagnostics/detail.headers/memory/ranges_construct_at.module.verify.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_at.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_n.pass.cpp

Modified: 
    libcxx/docs/Status/RangesAlgorithms.csv
    libcxx/docs/Status/RangesPaper.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__memory/construct_at.h
    libcxx/include/memory
    libcxx/include/module.modulemap
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/construct_at.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_at.pass.cpp
    libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_n.pass.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesAlgorithms.csv b/libcxx/docs/Status/RangesAlgorithms.csv
index 2636fca2d838d..155e6b880fb7e 100644
--- a/libcxx/docs/Status/RangesAlgorithms.csv
+++ b/libcxx/docs/Status/RangesAlgorithms.csv
@@ -94,7 +94,7 @@ Uninitialised memory,uninitialized_default_construct,Konstantin Varlamov,`D11531
 Uninitialised memory,uninitialized_default_construct_n,Konstantin Varlamov,`D115315 <https://llvm.org/D115315>`_,✅
 Uninitialised memory,uninitialized_value_construct,Konstantin Varlamov,`D115626 <https://llvm.org/D115626>`_,✅
 Uninitialised memory,uninitialized_value_construct_n,Konstantin Varlamov,`D115626 <https://llvm.org/D115626>`_,✅
-Uninitialised memory,destroy,Konstantin Varlamov,n/a,Not started
-Uninitialised memory,destroy_n,Konstantin Varlamov,n/a,Not started
-Uninitialised memory,destroy_at,Konstantin Varlamov,n/a,Not started
-Uninitialised memory,construct_at,Konstantin Varlamov,n/a,Not started
+Uninitialised memory,destroy,Konstantin Varlamov,`D116078 <https://llvm.org/D116078>`_,✅
+Uninitialised memory,destroy_n,Konstantin Varlamov,`D116078 <https://llvm.org/D116078>`_,✅
+Uninitialised memory,destroy_at,Konstantin Varlamov,`D116078 <https://llvm.org/D116078>`_,✅
+Uninitialised memory,construct_at,Konstantin Varlamov,`D116078 <https://llvm.org/D116078>`_,✅

diff  --git a/libcxx/docs/Status/RangesPaper.csv b/libcxx/docs/Status/RangesPaper.csv
index ef6aacc0ac523..be82f3fa6d4c1 100644
--- a/libcxx/docs/Status/RangesPaper.csv
+++ b/libcxx/docs/Status/RangesPaper.csv
@@ -28,10 +28,10 @@ Section,Description,Dependencies,Assignee,Complete
 | `ranges::uninitialized_move_n <https://llvm.org/D116023>`_
 | `ranges::uninitialized_fill <https://llvm.org/D115626>`_
 | `ranges::uninitialized_fill_n <https://llvm.org/D115626>`_
-| ranges::construct_at
-| ranges::destroy
-| ranges::destroy_at
-| ranges::destroy_n",[special.mem.concepts],Konstantin Varlamov,In progress
+| `ranges::construct_at <https://llvm.org/D116078>`_
+| `ranges::destroy <https://llvm.org/D116078>`_
+| `ranges::destroy_at <https://llvm.org/D116078>`_
+| `ranges::destroy_n <https://llvm.org/D116078>`_",[special.mem.concepts],Konstantin Varlamov,✅
 [strings],Adds begin/end and updates const_iterator.,[iterator.concepts],Unassigned,Not started
 [views.span],Same as [strings],[iterator.concepts],Unassigned,Not started
 `[iterator.cust.move] <http://wg21.link/iterator.cust.move>`_,`ranges::iter_move <https://llvm.org/D99873>`_,,Zoe Carver,✅

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 7efe0383d80b6..30708599751cd 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -249,6 +249,7 @@ set(files
   __memory/concepts.h
   __memory/construct_at.h
   __memory/pointer_traits.h
+  __memory/ranges_construct_at.h
   __memory/ranges_uninitialized_algorithms.h
   __memory/raw_storage_iterator.h
   __memory/shared_ptr.h

diff  --git a/libcxx/include/__memory/construct_at.h b/libcxx/include/__memory/construct_at.h
index 3b58451c5009f..580ce781f482d 100644
--- a/libcxx/include/__memory/construct_at.h
+++ b/libcxx/include/__memory/construct_at.h
@@ -14,6 +14,7 @@
 #include <__debug>
 #include <__iterator/access.h>
 #include <__memory/addressof.h>
+#include <__memory/voidify.h>
 #include <__utility/forward.h>
 #include <type_traits>
 #include <utility>
@@ -31,10 +32,10 @@ _LIBCPP_BEGIN_NAMESPACE_STD
 template<class _Tp, class ..._Args, class = decltype(
     ::new (declval<void*>()) _Tp(declval<_Args>()...)
 )>
-_LIBCPP_INLINE_VISIBILITY
+_LIBCPP_HIDE_FROM_ABI
 constexpr _Tp* construct_at(_Tp* __location, _Args&& ...__args) {
     _LIBCPP_ASSERT(__location, "null pointer given to construct_at");
-    return ::new ((void*)__location) _Tp(_VSTD::forward<_Args>(__args)...);
+    return ::new (_VSTD::__voidify(*__location)) _Tp(_VSTD::forward<_Args>(__args)...);
 }
 
 #endif
@@ -46,7 +47,7 @@ constexpr _Tp* construct_at(_Tp* __location, _Args&& ...__args) {
 
 template <class _ForwardIterator>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
-void __destroy(_ForwardIterator, _ForwardIterator);
+_ForwardIterator __destroy(_ForwardIterator, _ForwardIterator);
 
 template <class _Tp, typename enable_if<!is_array<_Tp>::value, int>::type = 0>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
@@ -66,9 +67,10 @@ void __destroy_at(_Tp* __loc) {
 
 template <class _ForwardIterator>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
-void __destroy(_ForwardIterator __first, _ForwardIterator __last) {
+_ForwardIterator __destroy(_ForwardIterator __first, _ForwardIterator __last) {
     for (; __first != __last; ++__first)
         _VSTD::__destroy_at(_VSTD::addressof(*__first));
+    return __first;
 }
 
 #if _LIBCPP_STD_VER > 14
@@ -90,7 +92,7 @@ void destroy_at(_Tp* __loc) {
 template <class _ForwardIterator>
 _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_AFTER_CXX17
 void destroy(_ForwardIterator __first, _ForwardIterator __last) {
-    _VSTD::__destroy(_VSTD::move(__first), _VSTD::move(__last));
+  (void)_VSTD::__destroy(_VSTD::move(__first), _VSTD::move(__last));
 }
 
 template <class _ForwardIterator, class _Size>

diff  --git a/libcxx/include/__memory/ranges_construct_at.h b/libcxx/include/__memory/ranges_construct_at.h
new file mode 100644
index 0000000000000..99ae299089d30
--- /dev/null
+++ b/libcxx/include/__memory/ranges_construct_at.h
@@ -0,0 +1,144 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___MEMORY_RANGES_CONSTRUCT_AT_H
+#define _LIBCPP___MEMORY_RANGES_CONSTRUCT_AT_H
+
+#include <__concepts/destructible.h>
+#include <__config>
+#include <__function_like.h>
+#include <__iterator/incrementable_traits.h>
+#include <__iterator/readable_traits.h>
+#include <__memory/concepts.h>
+#include <__memory/construct_at.h>
+#include <__ranges/access.h>
+#include <__ranges/concepts.h>
+#include <__ranges/dangling.h>
+#include <__utility/declval.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_RANGES)
+namespace ranges {
+
+// construct_at
+
+namespace __construct_at {
+
+struct __fn final : private __function_like {
+
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  template<class _Tp, class... _Args, class = decltype(
+    ::new (declval<void*>()) _Tp(declval<_Args>()...)
+  )>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr _Tp* operator()(_Tp* __location, _Args&& ...__args) const {
+    return _VSTD::construct_at(__location, _VSTD::forward<_Args>(__args)...);
+  }
+
+};
+
+} // namespace __construct_at
+
+inline namespace __cpo {
+inline constexpr auto construct_at = __construct_at::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// destroy_at
+
+namespace __destroy_at {
+
+struct __fn final : private __function_like {
+
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  template <destructible _Tp>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr void operator()(_Tp* __location) const noexcept {
+    _VSTD::destroy_at(__location);
+  }
+
+};
+
+} // namespace __destroy_at
+
+inline namespace __cpo {
+inline constexpr auto destroy_at = __destroy_at::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// destroy
+
+namespace __destroy {
+
+struct __fn final : private __function_like {
+
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  template <__nothrow_input_iterator _InputIterator, __nothrow_sentinel_for<_InputIterator> _Sentinel>
+    requires destructible<iter_value_t<_InputIterator>>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr _InputIterator operator()(_InputIterator __first, _Sentinel __last) const noexcept {
+    return _VSTD::__destroy(_VSTD::move(__first), _VSTD::move(__last));
+  }
+
+  template <__nothrow_input_range _InputRange>
+    requires destructible<range_value_t<_InputRange>>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr borrowed_iterator_t<_InputRange> operator()(_InputRange&& __range) const noexcept {
+    return (*this)(ranges::begin(__range), ranges::end(__range));
+  }
+
+};
+
+} // namespace __destroy
+
+inline namespace __cpo {
+inline constexpr auto destroy = __destroy::__fn(__function_like::__tag());
+} // namespace __cpo
+
+// destroy_n
+
+namespace __destroy_n {
+
+struct __fn final : private __function_like {
+
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr explicit __fn(__tag __x) noexcept : __function_like(__x) {}
+
+  template <__nothrow_input_iterator _InputIterator>
+    requires destructible<iter_value_t<_InputIterator>>
+  _LIBCPP_HIDE_FROM_ABI
+  constexpr _InputIterator operator()(_InputIterator __first, iter_
diff erence_t<_InputIterator> __n) const noexcept {
+    return _VSTD::destroy_n(_VSTD::move(__first), __n);
+  }
+
+};
+
+} // namespace __destroy_n
+
+inline namespace __cpo {
+inline constexpr auto destroy_n = __destroy_n::__fn(__function_like::__tag());
+} // namespace __cpo
+
+} // namespace ranges
+#endif // !defined(_LIBCPP_HAS_NO_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___MEMORY_RANGES_CONSTRUCT_AT_H

diff  --git a/libcxx/include/memory b/libcxx/include/memory
index 244b40fe7720d..db59936707e40 100644
--- a/libcxx/include/memory
+++ b/libcxx/include/memory
@@ -244,15 +244,40 @@ ForwardIterator uninitialized_fill_n(ForwardIterator first, iter_
diff erence_t<Fo
 template <class T, class ...Args>
 constexpr T* construct_at(T* location, Args&& ...args); // since C++20
 
+namespace ranges {
+  template<class T, class... Args>
+    constexpr T* construct_at(T* location, Args&&... args); // since C++20
+}
+
 template <class T>
 void destroy_at(T* location); // constexpr in C++20
 
+namespace ranges {
+  template<destructible T>
+    constexpr void destroy_at(T* location) noexcept; // since C++20
+}
+
 template <class ForwardIterator>
 void destroy(ForwardIterator first, ForwardIterator last); // constexpr in C++20
 
+namespace ranges {
+  template<nothrow-input-iterator InputIterator, nothrow-sentinel-for<InputIterator> Sentinel>
+    requires destructible<iter_value_t<InputIterator>>
+    constexpr InputIterator destroy(InputIterator first, Sentinel last) noexcept; // since C++20
+  template<nothrow-input-range InputRange>
+    requires destructible<range_value_t<InputRange>>
+    constexpr borrowed_iterator_t<InputRange> destroy(InputRange&& range) noexcept; // since C++20
+}
+
 template <class ForwardIterator, class Size>
 ForwardIterator destroy_n(ForwardIterator first, Size n); // constexpr in C++20
 
+namespace ranges {
+  template<nothrow-input-iterator InputIterator>
+    requires destructible<iter_value_t<InputIterator>>
+    constexpr InputIterator destroy_n(InputIterator first, iter_
diff erence_t<InputIterator> n) noexcept; // since C++20
+}
+
 template <class InputIterator, class ForwardIterator>
  ForwardIterator uninitialized_move(InputIterator first, InputIterator last, ForwardIterator result);
 
@@ -790,6 +815,7 @@ void* align(size_t alignment, size_t size, void*& ptr, size_t& space);
 #include <__memory/concepts.h>
 #include <__memory/construct_at.h>
 #include <__memory/pointer_traits.h>
+#include <__memory/ranges_construct_at.h>
 #include <__memory/ranges_uninitialized_algorithms.h>
 #include <__memory/raw_storage_iterator.h>
 #include <__memory/shared_ptr.h>

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 305fe1bc9d35f..828c9d55495fd 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -661,6 +661,10 @@ module std [system] {
       module concepts                        { private header "__memory/concepts.h" }
       module construct_at                    { private header "__memory/construct_at.h" }
       module pointer_traits                  { private header "__memory/pointer_traits.h" }
+      module ranges_construct_at {
+        private header "__memory/ranges_construct_at.h"
+        export __function_like
+      }
       module ranges_uninitialized_algorithms {
         private header "__memory/ranges_uninitialized_algorithms.h"
         export __function_like

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/memory/ranges_construct_at.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/memory/ranges_construct_at.module.verify.cpp
new file mode 100644
index 0000000000000..1aca1e47f6ddc
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/memory/ranges_construct_at.module.verify.cpp
@@ -0,0 +1,15 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__memory/ranges_construct_at.h'}}
+#include <__memory/ranges_construct_at.h>

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 06c6d0ef1db17..e7fd797966710 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
@@ -18,7 +18,9 @@
 
 #include <memory>
 #include <cassert>
+#include <utility>
 
+#include "test_iterators.h"
 
 struct Foo {
     int a;
@@ -97,15 +99,23 @@ constexpr bool test()
     return true;
 }
 
-// Make sure std::construct_at SFINAEs out based on the validity of calling
-// the constructor, instead of hard-erroring.
-template <typename T, typename = decltype(
-    std::construct_at((T*)nullptr, 1, 2) // missing arguments for Foo(...)
-)>
-constexpr bool test_sfinae(int) { return false; }
-template <typename T>
-constexpr bool test_sfinae(...) { return true; }
-static_assert(test_sfinae<Foo>(int()));
+template <class ...Args, class = decltype(std::construct_at(std::declval<Args>()...))>
+constexpr bool can_construct_at(Args&&...) { return true; }
+
+template <class ...Args>
+constexpr bool can_construct_at(...) { return false; }
+
+// Check that SFINAE works.
+static_assert( can_construct_at((int*)nullptr, 42));
+static_assert( can_construct_at((Foo*)nullptr, 1, '2', 3.0));
+static_assert(!can_construct_at((Foo*)nullptr, 1, '2'));
+static_assert(!can_construct_at((Foo*)nullptr, 1, '2', 3.0, 4));
+static_assert(!can_construct_at(nullptr, 1, '2', 3.0));
+static_assert(!can_construct_at((int*)nullptr, 1, '2', 3.0));
+static_assert(!can_construct_at(contiguous_iterator<Foo*>(), 1, '2', 3.0));
+// Can't construct function pointers.
+static_assert(!can_construct_at((int(*)())nullptr));
+static_assert(!can_construct_at((int(*)())nullptr, nullptr));
 
 int main(int, char**)
 {

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
new file mode 100644
index 0000000000000..11a78f3e2cd07
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.construct/ranges_construct_at.pass.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// namespace ranges {
+//   template<class T, class... Args>
+//     constexpr T* construct_at(T* location, Args&&... args); // since C++20
+// }
+
+#include <cassert>
+#include <initializer_list>
+#include <memory>
+#include <type_traits>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::construct_at)>);
+
+struct Foo {
+  int x = 0;
+  int y = 0;
+
+  constexpr Foo() = default;
+  constexpr explicit Foo(int set_x, int set_y) : x(set_x), y(set_y) {}
+  constexpr Foo(std::initializer_list<int>);
+
+  void operator&() const = delete;
+  void operator,(auto&&) const = delete;
+};
+
+ASSERT_SAME_TYPE(decltype(std::ranges::construct_at((int*)nullptr)), int*);
+ASSERT_SAME_TYPE(decltype(std::ranges::construct_at((Foo*)nullptr)), Foo*);
+
+struct Counted {
+  int& count;
+
+  constexpr Counted(int& count_ref) : count(count_ref) { ++count; }
+  constexpr Counted(const Counted& rhs) : count(rhs.count) { ++count; }
+  constexpr ~Counted() { --count; }
+};
+
+constexpr bool test() {
+  // Value initialization.
+  {
+    int x = 1;
+
+    int* result = std::ranges::construct_at(&x);
+    assert(result == &x);
+    assert(x == 0);
+  }
+
+  // Copy initialization.
+  {
+    int x = 1;
+
+    int* result = std::ranges::construct_at(&x, 42);
+    assert(result == &x);
+    assert(x == 42);
+  }
+
+  // Explicit multiargument constructor; also checks that the initializer list constructor is not invoked.
+  {
+    Foo f;
+
+    Foo* result = std::ranges::construct_at(std::addressof(f), 42, 123);
+    assert(result == std::addressof(f));
+    assert(f.x == 42);
+    assert(f.y == 123);
+  }
+
+  // Works with buffers of uninitialized memory.
+  {
+    std::allocator<Counted> alloc;
+    Counted* out = alloc.allocate(2);
+    int count = 0;
+
+    Counted* result = std::ranges::construct_at(out, count);
+    assert(result == out);
+    assert(count == 1);
+
+    result = std::ranges::construct_at(out + 1, count);
+    assert(result == out + 1);
+    assert(count == 2);
+
+    std::destroy(out, out + 1);
+    alloc.deallocate(out, 2);
+  }
+
+  // Works with const pointers.
+  {
+    int x = 1;
+    const int* ptr = &x;
+
+    const int* result = std::ranges::construct_at(ptr, 42);
+    assert(result == ptr);
+    assert(x == 42);
+  }
+
+  return true;
+}
+
+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; }
+
+// 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));
+// 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.
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy.pass.cpp
index f67c76ed003a0..b655a234e9957 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy.pass.cpp
@@ -109,7 +109,7 @@ int main(int, char**) {
     test_arrays();
     static_assert(tests());
     // TODO: Until std::construct_at has support for arrays, it's impossible to test this
-    //       in a constexpr context.
+    //       in a constexpr context (see https://reviews.llvm.org/D114903).
     // static_assert(test_arrays());
 #endif
     return 0;

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_at.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_at.pass.cpp
index 8b56cb9cd1c1f..894b0554cb5fe 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_at.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_at.pass.cpp
@@ -30,13 +30,12 @@ struct VirtualCounted {
     int* counter_;
     TEST_CONSTEXPR VirtualCounted(int* counter) : counter_(counter) { ++*counter_; }
     TEST_CONSTEXPR_CXX20 virtual ~VirtualCounted() { --*counter_; }
-    friend void operator&(VirtualCounted) = delete;
+    void operator&() const = delete;
 };
 
 struct DerivedCounted : VirtualCounted {
     TEST_CONSTEXPR DerivedCounted(int* counter) : VirtualCounted(counter) { }
     TEST_CONSTEXPR_CXX20 ~DerivedCounted() override { }
-    friend void operator&(DerivedCounted) = delete;
 };
 
 #if TEST_STD_VER > 17
@@ -135,7 +134,7 @@ int main(int, char**) {
     test_arrays();
     static_assert(test());
     // TODO: Until std::construct_at has support for arrays, it's impossible to test this
-    //       in a constexpr context.
+    //       in a constexpr context (see https://reviews.llvm.org/D114903).
     // static_assert(test_arrays());
 #endif
     return 0;

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_n.pass.cpp
index 80421cddb0da7..4eb719a94e4fe 100644
--- a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_n.pass.cpp
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/destroy_n.pass.cpp
@@ -112,7 +112,7 @@ int main(int, char**) {
     test_arrays();
     static_assert(tests());
     // TODO: Until std::construct_at has support for arrays, it's impossible to test this
-    //       in a constexpr context.
+    //       in a constexpr context (see https://reviews.llvm.org/D114903).
     // static_assert(test_arrays());
 #endif
     return 0;

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy.pass.cpp
new file mode 100644
index 0000000000000..46e70458dd9dd
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy.pass.cpp
@@ -0,0 +1,222 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// namespace ranges {
+//   template<nothrow-input-iterator InputIterator, nothrow-sentinel-for<InputIterator> Sentinel>
+//     requires destructible<iter_value_t<InputIterator>>
+//     constexpr InputIterator destroy(InputIterator first, Sentinel last) noexcept; // since C++20
+//   template<nothrow-input-range InputRange>
+//     requires destructible<range_value_t<InputRange>>
+//     constexpr borrowed_iterator_t<InputRange> destroy(InputRange&& range) noexcept; // since C++20
+// }
+
+#include <cassert>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::destroy)>);
+
+struct NotNothrowDtrable {
+  ~NotNothrowDtrable() noexcept(false) {}
+};
+static_assert(!std::is_invocable_v<decltype(std::ranges::destroy), NotNothrowDtrable*, NotNothrowDtrable*>);
+
+struct Counted {
+  int& count;
+
+  constexpr Counted(int& count_ref) : count(count_ref) { ++count; }
+  constexpr Counted(const Counted& rhs) : count(rhs.count) { ++count; }
+  constexpr ~Counted() { --count; }
+
+  friend void operator&(Counted) = delete;
+};
+
+template <class Iterator>
+constexpr void test() {
+  // (iterator + sentinel) overload.
+  {
+    constexpr int N = 5;
+    std::allocator<Counted> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Counted* out = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Traits::construct(alloc, out + i, counter);
+    }
+    assert(counter == N);
+
+    std::ranges::destroy(Iterator(out), Iterator(out + N));
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, out, N);
+  }
+
+  // (range) overload.
+  {
+    constexpr int N = 5;
+    std::allocator<Counted> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Counted* out = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Traits::construct(alloc, out + i, counter);
+    }
+    assert(counter == N);
+
+    auto range = std::ranges::subrange(Iterator(out), Iterator(out + N));
+    std::ranges::destroy(range);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, out, N);
+  }
+}
+
+constexpr bool tests() {
+  test<Counted*>();
+  test<forward_iterator<Counted*>>();
+
+  return true;
+}
+
+constexpr bool test_arrays() {
+  // One-dimensional array, (iterator + sentinel) overload.
+  {
+    constexpr int N = 5;
+    constexpr int M = 3;
+
+    using Array = Counted[M];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* buffer = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Array& array_ref = *(buffer + i);
+      for (int j = 0; j != M; ++j) {
+        Traits::construct(alloc, std::addressof(array_ref[j]), counter);
+      }
+    }
+    assert(counter == N * M);
+
+    std::ranges::destroy(buffer, buffer + N);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, N);
+  }
+
+  // One-dimensional array, (range) overload.
+  {
+    constexpr int N = 5;
+    constexpr int A = 3;
+
+    using Array = Counted[A];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* buffer = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Array& array_ref = *(buffer + i);
+      for (int j = 0; j != A; ++j) {
+        Traits::construct(alloc, std::addressof(array_ref[j]), counter);
+      }
+    }
+    assert(counter == N * A);
+
+    auto range = std::ranges::subrange(buffer, buffer + N);
+    std::ranges::destroy(range);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, N);
+  }
+
+  // Multidimensional array, (iterator + sentinel ) overload.
+  {
+    constexpr int N = 5;
+    constexpr int A = 3;
+    constexpr int B = 3;
+
+    using Array = Counted[A][B];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* buffer = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Array& array_ref = *(buffer + i);
+      for (int j = 0; j != A; ++j) {
+        for (int k = 0; k != B; ++k) {
+          Traits::construct(alloc, std::addressof(array_ref[j][k]), counter);
+        }
+      }
+    }
+    assert(counter == N * A * B);
+
+    std::ranges::destroy(buffer, buffer + N);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, N);
+  }
+
+  // Multidimensional array, (range) overload.
+  {
+    constexpr int N = 5;
+    constexpr int A = 3;
+    constexpr int B = 3;
+
+    using Array = Counted[A][B];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* buffer = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Array& array_ref = *(buffer + i);
+      for (int j = 0; j != A; ++j) {
+        for (int k = 0; k != B; ++k) {
+          Traits::construct(alloc, std::addressof(array_ref[j][k]), counter);
+        }
+      }
+    }
+    assert(counter == N * A * B);
+
+    std::ranges::destroy(buffer, buffer + N);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, N);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  test_arrays();
+
+  static_assert(tests());
+  // TODO: Until std::construct_at has support for arrays, it's impossible to test this
+  //       in a constexpr context (see https://reviews.llvm.org/D114903).
+  // static_assert(test_arrays());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_at.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_at.pass.cpp
new file mode 100644
index 0000000000000..3ab1d15d576e8
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_at.pass.cpp
@@ -0,0 +1,160 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// namespace ranges {
+//   template<destructible T>
+//     constexpr void destroy_at(T* location) noexcept; // since C++20
+// }
+
+#include <cassert>
+#include <memory>
+#include <type_traits>
+
+#include "test_macros.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::destroy_at)>);
+
+struct NotNothrowDtrable {
+  ~NotNothrowDtrable() noexcept(false) {}
+};
+static_assert(!std::is_invocable_v<decltype(std::ranges::destroy_at), NotNothrowDtrable*>);
+
+struct Counted {
+  int& count;
+
+  constexpr Counted(int& count_ref) : count(count_ref) { ++count; }
+  constexpr ~Counted() { --count; }
+
+  friend void operator&(Counted) = delete;
+};
+
+struct VirtualCountedBase {
+  int& count;
+
+  constexpr VirtualCountedBase(int& count_ref) : count(count_ref) { ++count; }
+  constexpr virtual ~VirtualCountedBase() { --count; }
+
+  void operator&() const = delete;
+};
+
+struct VirtualCountedDerived : VirtualCountedBase {
+  constexpr VirtualCountedDerived(int& count_ref) : VirtualCountedBase(count_ref) {}
+
+  // Without a definition, GCC gives an error when the destructor is invoked in a constexpr context (see
+  // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93413).
+  constexpr ~VirtualCountedDerived() override {}
+};
+
+constexpr bool test() {
+  // Destroying a "trivial" object.
+  {
+    std::allocator<Counted> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Counted* buffer = Traits::allocate(alloc, 2);
+    Traits::construct(alloc, buffer, counter);
+    Traits::construct(alloc, buffer + 1, counter);
+    assert(counter == 2);
+
+    std::ranges::destroy_at(buffer);
+    assert(counter == 1);
+    std::ranges::destroy_at(buffer + 1);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, 2);
+  }
+
+  // Destroying a derived object with a virtual destructor.
+  {
+    std::allocator<VirtualCountedDerived> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    VirtualCountedDerived* buffer = Traits::allocate(alloc, 2);
+    Traits::construct(alloc, buffer, counter);
+    Traits::construct(alloc, buffer + 1, counter);
+    assert(counter == 2);
+
+    std::ranges::destroy_at(buffer);
+    assert(counter == 1);
+    std::ranges::destroy_at(buffer + 1);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, 2);
+  }
+
+  return true;
+}
+
+constexpr bool test_arrays() {
+  // Pointer to an array.
+  {
+    using Array = Counted[3];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* array = Traits::allocate(alloc, 1);
+    Array& array_ref = *array;
+    for (int i = 0; i != 3; ++i) {
+      Traits::construct(alloc, std::addressof(array_ref[i]), counter);
+    }
+    assert(counter == 3);
+
+    std::ranges::destroy_at(array);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, array, 1);
+  }
+
+  // Pointer to a two-dimensional array.
+  {
+    using Array = Counted[3][2];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* array = Traits::allocate(alloc, 1);
+    Array& array_ref = *array;
+    for (int i = 0; i != 3; ++i) {
+      for (int j = 0; j != 2; ++j) {
+        Traits::construct(alloc, std::addressof(array_ref[i][j]), counter);
+      }
+    }
+    assert(counter == 3 * 2);
+
+    std::ranges::destroy_at(array);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, array, 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  test_arrays();
+
+  static_assert(test());
+  // TODO: Until std::construct_at has support for arrays, it's impossible to test this
+  //       in a constexpr context (see https://reviews.llvm.org/D114903).
+  // static_assert(test_arrays());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_n.pass.cpp b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_n.pass.cpp
new file mode 100644
index 0000000000000..13e3e7a639f21
--- /dev/null
+++ b/libcxx/test/std/utilities/memory/specialized.algorithms/specialized.destroy/ranges_destroy_n.pass.cpp
@@ -0,0 +1,144 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts, libcpp-has-no-incomplete-ranges
+
+// <memory>
+//
+// namespace ranges {
+//   template<nothrow-input-iterator InputIterator>
+//     requires destructible<iter_value_t<InputIterator>>
+//     constexpr InputIterator destroy_n(InputIterator first, iter_
diff erence_t<InputIterator> n) noexcept; // since C++20
+// }
+
+#include <cassert>
+#include <memory>
+#include <ranges>
+#include <type_traits>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+
+// TODO(varconst): consolidate the ADL checks into a single file.
+// Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
+// implementations are allowed to use a 
diff erent mechanism to achieve this effect, so this check is
+// libc++-specific.
+LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::destroy_n)>);
+
+struct NotNothrowDtrable {
+  ~NotNothrowDtrable() noexcept(false) {}
+};
+static_assert(!std::is_invocable_v<decltype(std::ranges::destroy_n), NotNothrowDtrable*, int>);
+
+struct Counted {
+  int& count;
+
+  constexpr Counted(int& count_ref) : count(count_ref) { ++count; }
+  constexpr Counted(const Counted& rhs) : count(rhs.count) { ++count; }
+  constexpr ~Counted() { --count; }
+
+  friend void operator&(Counted) = delete;
+};
+
+template <class Iterator>
+constexpr void test() {
+  {
+    constexpr int N = 5;
+    std::allocator<Counted> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Counted* out = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Traits::construct(alloc, out + i, counter);
+    }
+    assert(counter == N);
+
+    std::ranges::destroy_n(Iterator(out), N);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, out, N);
+  }
+}
+
+constexpr bool tests() {
+  test<Counted*>();
+  test<forward_iterator<Counted*>>();
+
+  return true;
+}
+
+constexpr bool test_arrays() {
+  // One-dimensional array.
+  {
+    constexpr int N = 5;
+    constexpr int M = 3;
+
+    using Array = Counted[M];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* buffer = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Array& array_ref = *(buffer + i);
+      for (int j = 0; j != M; ++j) {
+        Traits::construct(alloc, std::addressof(array_ref[j]), counter);
+      }
+    }
+    assert(counter == N * M);
+
+    std::ranges::destroy_n(buffer, N);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, N);
+  }
+
+  // Multidimensional array.
+  {
+    constexpr int N = 5;
+    constexpr int A = 3;
+    constexpr int B = 3;
+
+    using Array = Counted[A][B];
+    std::allocator<Array> alloc;
+    using Traits = std::allocator_traits<decltype(alloc)>;
+    int counter = 0;
+
+    Array* buffer = Traits::allocate(alloc, N);
+    for (int i = 0; i != N; ++i) {
+      Array& array_ref = *(buffer + i);
+      for (int j = 0; j != A; ++j) {
+        for (int k = 0; k != B; ++k) {
+          Traits::construct(alloc, std::addressof(array_ref[j][k]), counter);
+        }
+      }
+    }
+    assert(counter == N * A * B);
+
+    std::ranges::destroy_n(buffer, N);
+    assert(counter == 0);
+
+    Traits::deallocate(alloc, buffer, N);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  test_arrays();
+
+  static_assert(tests());
+  // TODO: Until std::construct_at has support for arrays, it's impossible to test this
+  //       in a constexpr context (see https://reviews.llvm.org/D114903).
+  // static_assert(test_arrays());
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list