1 | // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Marc Mutz <marc.mutz@kdab.com> |
2 | // Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> |
3 | // Copyright (C) 2020 The Qt Company Ltd. |
4 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
5 | |
6 | #if 0 |
7 | #pragma qt_sync_skip_header_check |
8 | #pragma qt_sync_stop_processing |
9 | #endif |
10 | |
11 | #ifndef QCONTAINERTOOLS_IMPL_H |
12 | #define QCONTAINERTOOLS_IMPL_H |
13 | |
14 | #include <QtCore/qglobal.h> |
15 | #include <QtCore/qtypeinfo.h> |
16 | |
17 | #include <QtCore/qxptype_traits.h> |
18 | |
19 | #include <cstring> |
20 | #include <iterator> |
21 | #include <memory> |
22 | #include <algorithm> |
23 | |
24 | QT_BEGIN_NAMESPACE |
25 | |
26 | namespace QtPrivate |
27 | { |
28 | |
29 | /*! |
30 | \internal |
31 | |
32 | Returns whether \a p is within a range [b, e). In simplest form equivalent to: |
33 | b <= p < e. |
34 | */ |
35 | template<typename T, typename Cmp = std::less<>> |
36 | static constexpr bool q_points_into_range(const T *p, const T *b, const T *e, |
37 | Cmp less = {}) noexcept |
38 | { |
39 | return !less(p, b) && less(p, e); |
40 | } |
41 | |
42 | /*! |
43 | \internal |
44 | |
45 | Returns whether \a p is within container \a c. In its simplest form equivalent to: |
46 | c.data() <= p < c.data() + c.size() |
47 | */ |
48 | template <typename C, typename T> |
49 | static constexpr bool q_points_into_range(const T &p, const C &c) noexcept |
50 | { |
51 | static_assert(std::is_same_v<decltype(std::data(c)), T>); |
52 | |
53 | // std::distance because QArrayDataPointer has a "qsizetype size" |
54 | // member but no size() function |
55 | return q_points_into_range(p, std::data(c), |
56 | std::data(c) + std::distance(std::begin(c), std::end(c))); |
57 | } |
58 | |
59 | QT_WARNING_PUSH |
60 | QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized" ) |
61 | |
62 | template <typename T, typename N> |
63 | void q_uninitialized_move_if_noexcept_n(T* first, N n, T* out) |
64 | { |
65 | if constexpr (std::is_nothrow_move_constructible_v<T> || !std::is_copy_constructible_v<T>) |
66 | std::uninitialized_move_n(first, n, out); |
67 | else |
68 | std::uninitialized_copy_n(first, n, out); |
69 | } |
70 | |
71 | template <typename T, typename N> |
72 | void q_uninitialized_relocate_n(T* first, N n, T* out) |
73 | { |
74 | if constexpr (QTypeInfo<T>::isRelocatable) { |
75 | if (n != N(0)) { // even if N == 0, out == nullptr or first == nullptr are UB for memcpy() |
76 | std::memcpy(dest: static_cast<void *>(out), |
77 | src: static_cast<const void *>(first), |
78 | n: n * sizeof(T)); |
79 | } |
80 | } else { |
81 | q_uninitialized_move_if_noexcept_n(first, n, out); |
82 | if constexpr (QTypeInfo<T>::isComplex) |
83 | std::destroy_n(first, n); |
84 | } |
85 | } |
86 | |
87 | QT_WARNING_POP |
88 | |
89 | /*! |
90 | \internal |
91 | |
92 | A wrapper around std::rotate(), with an optimization for |
93 | Q_RELOCATABLE_TYPEs. We omit the return value, as it would be more work to |
94 | compute in the Q_RELOCATABLE_TYPE case and, unlike std::rotate on |
95 | ForwardIterators, callers can compute the result in constant time |
96 | themselves. |
97 | */ |
98 | template <typename T> |
99 | void q_rotate(T *first, T *mid, T *last) |
100 | { |
101 | if constexpr (QTypeInfo<T>::isRelocatable) { |
102 | const auto cast = [](T *p) { return reinterpret_cast<uchar*>(p); }; |
103 | std::rotate(cast(first), cast(mid), cast(last)); |
104 | } else { |
105 | std::rotate(first, mid, last); |
106 | } |
107 | } |
108 | |
109 | /*! |
110 | \internal |
111 | Copies all elements, except the ones for which \a pred returns \c true, from |
112 | range [first, last), to the uninitialized memory buffer starting at \a out. |
113 | |
114 | It's undefined behavior if \a out points into [first, last). |
115 | |
116 | Returns a pointer one past the last copied element. |
117 | |
118 | If an exception is thrown, all the already copied elements in the destination |
119 | buffer are destroyed. |
120 | */ |
121 | template <typename T, typename Predicate> |
122 | T *q_uninitialized_remove_copy_if(T *first, T *last, T *out, Predicate &pred) |
123 | { |
124 | static_assert(std::is_nothrow_destructible_v<T>, |
125 | "This algorithm requires that T has a non-throwing destructor" ); |
126 | Q_ASSERT(!q_points_into_range(out, first, last)); |
127 | |
128 | T *dest_begin = out; |
129 | QT_TRY { |
130 | while (first != last) { |
131 | if (!pred(*first)) { |
132 | new (std::addressof(*out)) T(*first); |
133 | ++out; |
134 | } |
135 | ++first; |
136 | } |
137 | } QT_CATCH (...) { |
138 | std::destroy(std::reverse_iterator(out), std::reverse_iterator(dest_begin)); |
139 | QT_RETHROW; |
140 | } |
141 | return out; |
142 | } |
143 | |
144 | template<typename iterator, typename N> |
145 | void q_relocate_overlap_n_left_move(iterator first, N n, iterator d_first) |
146 | { |
147 | // requires: [first, n) is a valid range |
148 | // requires: d_first + n is reachable from d_first |
149 | // requires: iterator is at least a random access iterator |
150 | // requires: value_type(iterator) has a non-throwing destructor |
151 | |
152 | Q_ASSERT(n); |
153 | Q_ASSERT(d_first < first); // only allow moves to the "left" |
154 | using T = typename std::iterator_traits<iterator>::value_type; |
155 | |
156 | // Watches passed iterator. Unless commit() is called, all the elements that |
157 | // the watched iterator passes through are deleted at the end of object |
158 | // lifetime. freeze() could be used to stop watching the passed iterator and |
159 | // remain at current place. |
160 | // |
161 | // requires: the iterator is expected to always point to an invalid object |
162 | // (to uninitialized memory) |
163 | struct Destructor |
164 | { |
165 | iterator *iter; |
166 | iterator end; |
167 | iterator intermediate; |
168 | |
169 | Destructor(iterator &it) noexcept : iter(std::addressof(it)), end(it) { } |
170 | void commit() noexcept { iter = std::addressof(end); } |
171 | void freeze() noexcept |
172 | { |
173 | intermediate = *iter; |
174 | iter = std::addressof(intermediate); |
175 | } |
176 | ~Destructor() noexcept |
177 | { |
178 | for (const int step = *iter < end ? 1 : -1; *iter != end;) { |
179 | std::advance(*iter, step); |
180 | (*iter)->~T(); |
181 | } |
182 | } |
183 | } destroyer(d_first); |
184 | |
185 | const iterator d_last = d_first + n; |
186 | // Note: use pair and explicitly copy iterators from it to prevent |
187 | // accidental reference semantics instead of copy. equivalent to: |
188 | // |
189 | // auto [overlapBegin, overlapEnd] = std::minmax(d_last, first); |
190 | auto pair = std::minmax(d_last, first); |
191 | |
192 | // overlap area between [d_first, d_first + n) and [first, first + n) or an |
193 | // uninitialized memory area between the two ranges |
194 | iterator overlapBegin = pair.first; |
195 | iterator overlapEnd = pair.second; |
196 | |
197 | // move construct elements in uninitialized region |
198 | while (d_first != overlapBegin) { |
199 | // account for std::reverse_iterator, cannot use new(d_first) directly |
200 | new (std::addressof(*d_first)) T(std::move_if_noexcept(*first)); |
201 | ++d_first; |
202 | ++first; |
203 | } |
204 | |
205 | // cannot commit but have to stop - there might be an overlap region |
206 | // which we don't want to delete (because it's part of existing data) |
207 | destroyer.freeze(); |
208 | |
209 | // move assign elements in overlap region |
210 | while (d_first != d_last) { |
211 | *d_first = std::move_if_noexcept(*first); |
212 | ++d_first; |
213 | ++first; |
214 | } |
215 | |
216 | Q_ASSERT(d_first == destroyer.end + n); |
217 | destroyer.commit(); // can commit here as ~T() below does not throw |
218 | |
219 | while (first != overlapEnd) |
220 | (--first)->~T(); |
221 | } |
222 | |
223 | /*! |
224 | \internal |
225 | |
226 | Relocates a range [first, n) to [d_first, n) taking care of potential memory |
227 | overlaps. This is a generic equivalent of memmove. |
228 | |
229 | If an exception is thrown during the relocation, all the relocated elements |
230 | are destroyed and [first, n) may contain valid but unspecified values, |
231 | including moved-from values (basic exception safety). |
232 | */ |
233 | template<typename T, typename N> |
234 | void q_relocate_overlap_n(T *first, N n, T *d_first) |
235 | { |
236 | static_assert(std::is_nothrow_destructible_v<T>, |
237 | "This algorithm requires that T has a non-throwing destructor" ); |
238 | |
239 | if (n == N(0) || first == d_first || first == nullptr || d_first == nullptr) |
240 | return; |
241 | |
242 | if constexpr (QTypeInfo<T>::isRelocatable) { |
243 | std::memmove(dest: static_cast<void *>(d_first), src: static_cast<const void *>(first), n: n * sizeof(T)); |
244 | } else { // generic version has to be used |
245 | if (d_first < first) { |
246 | q_relocate_overlap_n_left_move(first, n, d_first); |
247 | } else { // first < d_first |
248 | auto rfirst = std::make_reverse_iterator(first + n); |
249 | auto rd_first = std::make_reverse_iterator(d_first + n); |
250 | q_relocate_overlap_n_left_move(rfirst, n, rd_first); |
251 | } |
252 | } |
253 | } |
254 | |
255 | template <typename Iterator> |
256 | using IfIsInputIterator = typename std::enable_if< |
257 | std::is_convertible<typename std::iterator_traits<Iterator>::iterator_category, std::input_iterator_tag>::value, |
258 | bool>::type; |
259 | |
260 | template <typename Iterator> |
261 | using IfIsForwardIterator = typename std::enable_if< |
262 | std::is_convertible<typename std::iterator_traits<Iterator>::iterator_category, std::forward_iterator_tag>::value, |
263 | bool>::type; |
264 | |
265 | template <typename Iterator> |
266 | using IfIsNotForwardIterator = typename std::enable_if< |
267 | !std::is_convertible<typename std::iterator_traits<Iterator>::iterator_category, std::forward_iterator_tag>::value, |
268 | bool>::type; |
269 | |
270 | template <typename Container, |
271 | typename InputIterator, |
272 | IfIsNotForwardIterator<InputIterator> = true> |
273 | void reserveIfForwardIterator(Container *, InputIterator, InputIterator) |
274 | { |
275 | } |
276 | |
277 | template <typename Container, |
278 | typename ForwardIterator, |
279 | IfIsForwardIterator<ForwardIterator> = true> |
280 | void reserveIfForwardIterator(Container *c, ForwardIterator f, ForwardIterator l) |
281 | { |
282 | c->reserve(static_cast<typename Container::size_type>(std::distance(f, l))); |
283 | } |
284 | |
285 | template <typename Iterator> |
286 | using KeyAndValueTest = decltype( |
287 | std::declval<Iterator &>().key(), |
288 | std::declval<Iterator &>().value() |
289 | ); |
290 | |
291 | template <typename Iterator> |
292 | using FirstAndSecondTest = decltype( |
293 | std::declval<Iterator &>()->first, |
294 | std::declval<Iterator &>()->second |
295 | ); |
296 | |
297 | template <typename Iterator> |
298 | using IfAssociativeIteratorHasKeyAndValue = |
299 | std::enable_if_t<qxp::is_detected_v<KeyAndValueTest, Iterator>, bool>; |
300 | |
301 | template <typename Iterator> |
302 | using IfAssociativeIteratorHasFirstAndSecond = |
303 | std::enable_if_t<qxp::is_detected_v<FirstAndSecondTest, Iterator>, bool>; |
304 | |
305 | template <typename Iterator> |
306 | using MoveBackwardsTest = decltype( |
307 | std::declval<Iterator &>().operator--() |
308 | ); |
309 | |
310 | template <typename Iterator> |
311 | using IfIteratorCanMoveBackwards = |
312 | std::enable_if_t<qxp::is_detected_v<MoveBackwardsTest, Iterator>, bool>; |
313 | |
314 | template <typename T, typename U> |
315 | using IfIsNotSame = |
316 | typename std::enable_if<!std::is_same<T, U>::value, bool>::type; |
317 | |
318 | template<typename T, typename U> |
319 | using IfIsNotConvertible = typename std::enable_if<!std::is_convertible<T, U>::value, bool>::type; |
320 | |
321 | template <typename Container, typename Predicate> |
322 | auto sequential_erase_if(Container &c, Predicate &pred) |
323 | { |
324 | // This is remove_if() modified to perform the find_if step on |
325 | // const_iterators to avoid shared container detaches if nothing needs to |
326 | // be removed. We cannot run remove_if after find_if: doing so would apply |
327 | // the predicate to the first matching element twice! |
328 | |
329 | const auto cbegin = c.cbegin(); |
330 | const auto cend = c.cend(); |
331 | const auto t_it = std::find_if(cbegin, cend, pred); |
332 | auto result = std::distance(cbegin, t_it); |
333 | if (result == c.size()) |
334 | return result - result; // `0` of the right type |
335 | |
336 | // now detach: |
337 | const auto e = c.end(); |
338 | |
339 | auto it = std::next(c.begin(), result); |
340 | auto dest = it; |
341 | |
342 | // Loop Invariants: |
343 | // - it != e |
344 | // - [next(it), e[ still to be checked |
345 | // - [c.begin(), dest[ are result |
346 | while (++it != e) { |
347 | if (!pred(*it)) { |
348 | *dest = std::move(*it); |
349 | ++dest; |
350 | } |
351 | } |
352 | |
353 | result = std::distance(dest, e); |
354 | c.erase(dest, e); |
355 | return result; |
356 | } |
357 | |
358 | template <typename Container, typename T> |
359 | auto sequential_erase(Container &c, const T &t) |
360 | { |
361 | // use the equivalence relation from http://eel.is/c++draft/list.erasure#1 |
362 | auto cmp = [&](auto &e) { return e == t; }; |
363 | return sequential_erase_if(c, cmp); // can't pass rvalues! |
364 | } |
365 | |
366 | template <typename Container, typename T> |
367 | auto sequential_erase_with_copy(Container &c, const T &t) |
368 | { |
369 | using CopyProxy = std::conditional_t<std::is_copy_constructible_v<T>, T, const T &>; |
370 | const T &tCopy = CopyProxy(t); |
371 | return sequential_erase(c, tCopy); |
372 | } |
373 | |
374 | template <typename Container, typename T> |
375 | auto sequential_erase_one(Container &c, const T &t) |
376 | { |
377 | const auto cend = c.cend(); |
378 | const auto it = std::find(c.cbegin(), cend, t); |
379 | if (it == cend) |
380 | return false; |
381 | c.erase(it); |
382 | return true; |
383 | } |
384 | |
385 | template <typename T, typename Predicate> |
386 | qsizetype qset_erase_if(QSet<T> &set, Predicate &pred) |
387 | { |
388 | qsizetype result = 0; |
389 | auto it = set.begin(); |
390 | const auto e = set.end(); |
391 | while (it != e) { |
392 | if (pred(*it)) { |
393 | ++result; |
394 | it = set.erase(it); |
395 | } else { |
396 | ++it; |
397 | } |
398 | } |
399 | return result; |
400 | } |
401 | |
402 | |
403 | // Prerequisite: F is invocable on ArgTypes |
404 | template <typename R, typename F, typename ... ArgTypes> |
405 | struct is_invoke_result_explicitly_convertible : std::is_constructible<R, std::invoke_result_t<F, ArgTypes...>> |
406 | {}; |
407 | |
408 | // is_invocable_r checks for implicit conversions, but we need to check |
409 | // for explicit conversions in remove_if. So, roll our own trait. |
410 | template <typename R, typename F, typename ... ArgTypes> |
411 | constexpr bool is_invocable_explicit_r_v = std::conjunction_v< |
412 | std::is_invocable<F, ArgTypes...>, |
413 | is_invoke_result_explicitly_convertible<R, F, ArgTypes...> |
414 | >; |
415 | |
416 | template <typename Container, typename Predicate> |
417 | auto associative_erase_if(Container &c, Predicate &pred) |
418 | { |
419 | // we support predicates callable with either Container::iterator |
420 | // or with std::pair<const Key &, Value &> |
421 | using Iterator = typename Container::iterator; |
422 | using Key = typename Container::key_type; |
423 | using Value = typename Container::mapped_type; |
424 | using KeyValuePair = std::pair<const Key &, Value &>; |
425 | |
426 | typename Container::size_type result = 0; |
427 | |
428 | auto it = c.begin(); |
429 | const auto e = c.end(); |
430 | while (it != e) { |
431 | if constexpr (is_invocable_explicit_r_v<bool, Predicate &, Iterator &>) { |
432 | if (pred(it)) { |
433 | it = c.erase(it); |
434 | ++result; |
435 | } else { |
436 | ++it; |
437 | } |
438 | } else if constexpr (is_invocable_explicit_r_v<bool, Predicate &, KeyValuePair &&>) { |
439 | KeyValuePair p(it.key(), it.value()); |
440 | if (pred(std::move(p))) { |
441 | it = c.erase(it); |
442 | ++result; |
443 | } else { |
444 | ++it; |
445 | } |
446 | } else { |
447 | static_assert(sizeof(Container) == 0, "Predicate has an incompatible signature" ); |
448 | } |
449 | } |
450 | |
451 | return result; |
452 | } |
453 | |
454 | } // namespace QtPrivate |
455 | |
456 | QT_END_NAMESPACE |
457 | |
458 | #endif // QCONTAINERTOOLS_IMPL_H |
459 | |