1 //===----------------------------------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 // UNSUPPORTED: c++03, c++11, c++14, c++17
10 // UNSUPPORTED: libcpp-has-no-incomplete-ranges
11 
12 // <memory>
13 //
14 // namespace ranges {
15 //   template<destructible T>
16 //     constexpr void destroy_at(T* location) noexcept; // since C++20
17 // }
18 
19 #include <cassert>
20 #include <memory>
21 #include <type_traits>
22 
23 #include "test_macros.h"
24 
25 // TODO(varconst): consolidate the ADL checks into a single file.
26 // Because this is a variable and not a function, it's guaranteed that ADL won't be used. However,
27 // implementations are allowed to use a different mechanism to achieve this effect, so this check is
28 // libc++-specific.
29 LIBCPP_STATIC_ASSERT(std::is_class_v<decltype(std::ranges::destroy_at)>);
30 
31 struct NotNothrowDtrable {
~NotNothrowDtrableNotNothrowDtrable32   ~NotNothrowDtrable() noexcept(false) {}
33 };
34 static_assert(!std::is_invocable_v<decltype(std::ranges::destroy_at), NotNothrowDtrable*>);
35 
36 struct Counted {
37   int& count;
38 
CountedCounted39   constexpr Counted(int& count_ref) : count(count_ref) { ++count; }
~CountedCounted40   constexpr ~Counted() { --count; }
41 
42   friend void operator&(Counted) = delete;
43 };
44 
45 struct VirtualCountedBase {
46   int& count;
47 
VirtualCountedBaseVirtualCountedBase48   constexpr VirtualCountedBase(int& count_ref) : count(count_ref) { ++count; }
~VirtualCountedBaseVirtualCountedBase49   constexpr virtual ~VirtualCountedBase() { --count; }
50 
51   void operator&() const = delete;
52 };
53 
54 struct VirtualCountedDerived : VirtualCountedBase {
VirtualCountedDerivedVirtualCountedDerived55   constexpr VirtualCountedDerived(int& count_ref) : VirtualCountedBase(count_ref) {}
56 
57   // Without a definition, GCC gives an error when the destructor is invoked in a constexpr context (see
58   // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93413).
~VirtualCountedDerivedVirtualCountedDerived59   constexpr ~VirtualCountedDerived() override {}
60 };
61 
test()62 constexpr bool test() {
63   // Destroying a "trivial" object.
64   {
65     std::allocator<Counted> alloc;
66     using Traits = std::allocator_traits<decltype(alloc)>;
67     int counter = 0;
68 
69     Counted* buffer = Traits::allocate(alloc, 2);
70     Traits::construct(alloc, buffer, counter);
71     Traits::construct(alloc, buffer + 1, counter);
72     assert(counter == 2);
73 
74     std::ranges::destroy_at(buffer);
75     assert(counter == 1);
76     std::ranges::destroy_at(buffer + 1);
77     assert(counter == 0);
78 
79     Traits::deallocate(alloc, buffer, 2);
80   }
81 
82   // Destroying a derived object with a virtual destructor.
83   {
84     std::allocator<VirtualCountedDerived> alloc;
85     using Traits = std::allocator_traits<decltype(alloc)>;
86     int counter = 0;
87 
88     VirtualCountedDerived* buffer = Traits::allocate(alloc, 2);
89     Traits::construct(alloc, buffer, counter);
90     Traits::construct(alloc, buffer + 1, counter);
91     assert(counter == 2);
92 
93     std::ranges::destroy_at(buffer);
94     assert(counter == 1);
95     std::ranges::destroy_at(buffer + 1);
96     assert(counter == 0);
97 
98     Traits::deallocate(alloc, buffer, 2);
99   }
100 
101   return true;
102 }
103 
test_arrays()104 constexpr bool test_arrays() {
105   // Pointer to an array.
106   {
107     using Array = Counted[3];
108     std::allocator<Array> alloc;
109     using Traits = std::allocator_traits<decltype(alloc)>;
110     int counter = 0;
111 
112     Array* array = Traits::allocate(alloc, 1);
113     Array& array_ref = *array;
114     for (int i = 0; i != 3; ++i) {
115       Traits::construct(alloc, std::addressof(array_ref[i]), counter);
116     }
117     assert(counter == 3);
118 
119     std::ranges::destroy_at(array);
120     assert(counter == 0);
121 
122     Traits::deallocate(alloc, array, 1);
123   }
124 
125   // Pointer to a two-dimensional array.
126   {
127     using Array = Counted[3][2];
128     std::allocator<Array> alloc;
129     using Traits = std::allocator_traits<decltype(alloc)>;
130     int counter = 0;
131 
132     Array* array = Traits::allocate(alloc, 1);
133     Array& array_ref = *array;
134     for (int i = 0; i != 3; ++i) {
135       for (int j = 0; j != 2; ++j) {
136         Traits::construct(alloc, std::addressof(array_ref[i][j]), counter);
137       }
138     }
139     assert(counter == 3 * 2);
140 
141     std::ranges::destroy_at(array);
142     assert(counter == 0);
143 
144     Traits::deallocate(alloc, array, 1);
145   }
146 
147   return true;
148 }
149 
main(int,char **)150 int main(int, char**) {
151   test();
152   test_arrays();
153 
154   static_assert(test());
155   // TODO: Until std::construct_at has support for arrays, it's impossible to test this
156   //       in a constexpr context (see https://reviews.llvm.org/D114903).
157   // static_assert(test_arrays());
158 
159   return 0;
160 }
161