OXYGENHUB
◆ main openmsx / src / utils / stl.hh
16.6 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#ifndef STL_HH
2
#define STL_HH
3
4
#include <algorithm>
5
#include <cassert>
6
#include <functional>
7
#include <initializer_list>
8
#include <iterator>
9
#include <map>
10
#include <memory>
11
#include <ranges>
12
#include <utility>
13
#include <variant>
14
#include <vector>
15
16
// shared between various other classes
17
struct uninitialized_tag {};
18
19
// Predicate that can be called with any number of parameters (of any type) and
20
// just always returns 'true'. This can be useful as a default parameter value.
21
struct always_true {
22
	template<typename ...Args>
23
	bool operator()(Args&& ...) const {
24
		return true;
25
	}
26
};
27
28
/** Check if a range contains a given value, using linear search.
29
  * Equivalent to 'find(first, last, val) != last', though this algorithm
30
  * is more convenient to use.
31
  * Note: we don't need a variant that uses 'find_if' instead of 'find' because
32
  * STL already has the 'any_of' algorithm.
33
  */
34
template<std::ranges::input_range Range, typename VAL, typename Proj = std::identity>
35
[[nodiscard]] constexpr bool contains(Range&& range, const VAL& val, Proj proj = {})
36
{
37
	auto first = std::ranges::begin(range);
38
	auto last = std::ranges::end(range);
39
	return std::ranges::find(first, last, val, proj) != last;
40
}
41
42
/** Faster alternative to 'find' when it's guaranteed that the value will be
43
  * found (if not the behavior is undefined).
44
  * When asserts are enabled we check whether we really don't move beyond the
45
  * end of the range. And this check is the only reason why you need to pass
46
  * the 'last' parameter. Sometimes you see 'find_unguarded' without a 'last'
47
  * parameter, we could consider providing such an overload as well.
48
  */
49
template<std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel, typename Val, typename Proj = std::identity>
50
[[nodiscard]] constexpr Iterator find_unguarded(Iterator first, Sentinel last, const Val& val, Proj proj = {})
51
{
52
	return find_if_unguarded(first, last,
53
		[&](const auto& e) { return std::invoke(proj, e) == val; });
54
}
55
template<std::ranges::input_range Range, typename Val, typename Proj = std::identity>
56
[[nodiscard]] constexpr auto find_unguarded(Range&& range, const Val& val, Proj proj = {})
57
{
58
	return find_unguarded(std::ranges::begin(range), std::ranges::end(range), val, proj);
59
}
60
61
/** Faster alternative to 'find_if' when it's guaranteed that the predicate
62
  * will be true for at least one element in the given range.
63
  * See also 'find_unguarded'.
64
  */
65
template<std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel, std::indirect_unary_predicate<Iterator> Predicate>
66
[[nodiscard]] constexpr Iterator find_if_unguarded(Iterator first, Sentinel last, Predicate pred)
67
{
68
	(void)last;
69
	while (true) {
70
		assert(first != last);
71
		if (pred(*first)) return first;
72
		++first;
73
	}
74
}
75
template<std::ranges::input_range Range, typename Predicate>
76
[[nodiscard]] constexpr auto find_if_unguarded(Range&& range, Predicate pred)
77
{
78
	return find_if_unguarded(std::ranges::begin(range), std::ranges::end(range), pred);
79
}
80
81
/** Similar to the find(_if)_unguarded functions above, but searches from the
82
  * back to front.
83
  * Note that we only need to provide range versions. Because for the iterator
84
  * versions it is already possible to pass reverse iterators.
85
  */
86
template<std::ranges::bidirectional_range Range, typename Val, typename Proj = std::identity>
87
[[nodiscard]] constexpr auto rfind_unguarded(Range&& range, const Val& val, Proj proj = {})
88
{
89
	auto it = find_unguarded(std::ranges::rbegin(range), std::ranges::rend(range), val, proj);
90
	++it;
91
	return it.base();
92
}
93
94
template<std::ranges::bidirectional_range Range, typename Predicate>
95
[[nodiscard]] constexpr auto rfind_if_unguarded(Range&& range, Predicate pred)
96
{
97
	auto it = find_if_unguarded(std::ranges::rbegin(range), std::ranges::rend(range), pred);
98
	++it;
99
	return it.base();
100
}
101
102
103
/** Erase the pointed to element from the given vector.
104
  *
105
  * We first move the last element into the indicated position and then pop
106
  * the last element.
107
  *
108
  * If the order of the elements in the vector is allowed to change this may
109
  * be a faster alternative than calling 'v.erase(it)'.
110
  */
111
template<typename VECTOR>
112
void move_pop_back(VECTOR& v, typename VECTOR::iterator it)
113
{
114
	// Check for self-move-assignment.
115
	//
116
	// This check is only needed in libstdc++ when compiled with
117
	// -D_GLIBCXX_DEBUG. In non-debug mode this routine works perfectly
118
	// fine without the check.
119
	//
120
	// See here for a related discussion:
121
	//    http://stackoverflow.com/questions/13129031/on-implementing-stdswap-in-terms-of-move-assignment-and-move-constructor
122
	// It's not clear whether the assert in libstdc++ is conforming
123
	// behavior.
124
	if (std::to_address(it) != &v.back()) {
125
		*it = std::move(v.back());
126
	}
127
	v.pop_back();
128
}
129
130
131
/** This is like a combination of partition_copy() and remove().
132
 *
133
  * Each element is tested against the predicate. If it matches, the element is
134
  * copied to the output and removed from the input. So the output contains all
135
  * elements for which the predicate gives true and the input is modified
136
  * in-place to contain the elements for which the predicate gives false. (Like
137
  * the remove() algorithm, it's still required to call erase() to also shrink
138
  * the input).
139
  *
140
  * This algorithm returns a pair of iterators.
141
  * -first: one past the matching elements (in the output range). Similar to
142
  *    the return value of the partition_copy() algorithm.
143
  * -second: one past the non-matching elements (in the input range). Similar
144
  *    to the return value of the remove() algorithm.
145
  */
146
template<std::forward_iterator ForwardIt, std::sentinel_for<ForwardIt> Sentinel,
147
         std::output_iterator<std::iter_value_t<ForwardIt>> OutputIt,
148
         std::indirect_unary_predicate<ForwardIt> Predicate>
149
[[nodiscard]] std::pair<OutputIt, ForwardIt> partition_copy_remove(
150
	ForwardIt first, Sentinel last, OutputIt out_true, Predicate p)
151
{
152
	first = std::find_if(first, last, p);
153
	auto out_false = first;
154
	if (first != last) {
155
		goto l_true;
156
		while (first != last) {
157
			if (p(*first)) {
158
l_true:				*out_true++  = std::move(*first++);
159
			} else {
160
				*out_false++ = std::move(*first++);
161
			}
162
		}
163
	}
164
	return std::pair(out_true, out_false);
165
}
166
167
template<std::ranges::forward_range Range,
168
         std::output_iterator<std::ranges::range_value_t<Range>> Output,
169
         std::predicate<std::ranges::range_value_t<Range>> UnaryPredicate>
170
[[nodiscard]] auto partition_copy_remove(Range&& range, Output out_true, UnaryPredicate p)
171
{
172
	return partition_copy_remove(std::ranges::begin(range), std::ranges::end(range), out_true, p);
173
}
174
175
176
// Like range::transform(), but with equal source and destination.
177
template<std::ranges::forward_range Range,
178
         std::invocable<std::ranges::range_value_t<Range>> Operation>
179
auto transform_in_place(Range&& range, Operation op)
180
{
181
	return std::ranges::transform(range, std::ranges::begin(range), op);
182
}
183
184
185
// Returns (a copy of) the minimum value in [first, last).
186
// Requires: first != last.
187
template<std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel, typename Proj = std::identity>
188
[[nodiscard]] constexpr auto min_value(Iterator first, Sentinel last, Proj proj = {})
189
{
190
	assert(first != last);
191
	auto result = std::invoke(proj, *first++);
192
	while (first != last) {
193
		result = std::min(result, std::invoke(proj, *first++));
194
	}
195
	return result;
196
}
197
198
template<std::ranges::input_range Range, typename Proj = std::identity>
199
[[nodiscard]] constexpr auto min_value(Range&& range, Proj proj = {})
200
{
201
	return min_value(std::ranges::begin(range), std::ranges::end(range), proj);
202
}
203
204
// Returns (a copy of) the maximum value in [first, last).
205
// Requires: first != last.
206
template<std::input_iterator Iterator, std::sentinel_for<Iterator> Sentinel, typename Proj = std::identity>
207
[[nodiscard]] constexpr auto max_value(Iterator first, Sentinel last, Proj proj = {})
208
{
209
	assert(first != last);
210
	auto result = std::invoke(proj, *first++);
211
	while (first != last) {
212
		result = std::max(result, std::invoke(proj, *first++));
213
	}
214
	return result;
215
}
216
217
template<std::ranges::input_range Range, typename Proj = std::identity>
218
[[nodiscard]] constexpr auto max_value(Range&& range, Proj proj = {})
219
{
220
	return max_value(std::ranges::begin(range), std::ranges::end(range), proj);
221
}
222
223
224
// Returns the sum of the elements in the given range.
225
// Assumes: elements can be summed via operator+, with a default constructed
226
// value being the identity-element for this operator.
227
template<std::ranges::input_range Range, typename Proj = std::identity>
228
[[nodiscard]] constexpr auto sum(Range&& range, Proj proj = {})
229
{
230
	using Iter = decltype(std::begin(range));
231
	using VT = typename std::iterator_traits<Iter>::value_type;
232
	using RT = decltype(std::invoke(proj, std::declval<VT>()));
233
234
	auto first = std::ranges::begin(range);
235
	auto last = std::ranges::end(range);
236
	RT init{};
237
	while (first != last) {
238
		init = std::move(init) + std::invoke(proj, *first++);
239
	}
240
	return init;
241
}
242
243
template<std::ranges::forward_range R,
244
         std::invocable<std::size_t, size_t, std::ranges::range_value_t<R> const&> Func>
245
void foreach_run(R&& r, Func&& func)
246
{
247
	auto it = std::ranges::begin(r);
248
	auto end = std::ranges::end(r);
249
	if (it == end) return;
250
251
	size_t index = 0;
252
253
	// first run
254
	size_t run_start = 0;
255
	size_t run_length = 1;
256
	auto run_value = *it;
257
	for (++it, ++index; it != end; ++it, ++index) {
258
		if (*it == run_value) {
259
			// extend current run
260
			++run_length;
261
		} else {
262
			// emit finished run
263
			std::invoke(func, run_start, run_length, run_value);
264
			// start new run
265
			run_start = index;
266
			run_value = *it;
267
			run_length = 1;
268
		}
269
	}
270
	// emit final run
271
	std::invoke(func, run_start, run_length, run_value);
272
}
273
274
275
// to_vector
276
namespace detail {
277
	template<typename T, typename Iterator>
278
	using ToVectorType = std::conditional_t<
279
		std::is_same_v<T, void>,
280
		typename std::iterator_traits<Iterator>::value_type,
281
		T>;
282
}
283
284
// Convert any range to a vector. Optionally specify the type of the elements
285
// in the result.
286
// Example:
287
//   auto v1 = to_vector(std::views::drop(my_list, 3));
288
//   auto v2 = to_vector<Base*>(getDerivedPtrs());
289
template<typename T = void, std::ranges::common_range Range,
290
         typename V = std::conditional_t<std::is_void_v<T>, std::ranges::range_value_t<Range>, T>>
291
[[nodiscard]] std::vector<V> to_vector(Range&& range)
292
{
293
#ifdef __cpp_lib_ranges_to_container
294
	return std::ranges::to<std::vector<V>>(std::forward<Range>(range));
295
#elifdef __cpp_lib_containers_ranges
296
	return {std::from_range, std::forward<Range>(range)};
297
#else
298
	// Cross fingers and hope the range's iterators meet Cpp17InputIterator requirements...
299
	return {std::ranges::begin(range), std::ranges::end(range)};
300
#endif
301
}
302
303
// Optimized version for r-value input and no type conversion.
304
template<typename T>
305
[[nodiscard]] auto to_vector(std::vector<T>&& v)
306
{
307
	return std::move(v);
308
}
309
310
311
// append() / concat()
312
namespace detail {
313
314
template<typename... Ranges>
315
[[nodiscard]] constexpr size_t sum_of_sizes(const Ranges&... ranges)
316
{
317
    return (0 + ... + std::distance(std::begin(ranges), std::end(ranges)));
318
}
319
320
template<typename Result>
321
void append(Result&)
322
{
323
	// nothing
324
}
325
326
template<typename Result, typename Range, typename... Tail>
327
void append(Result& x, Range&& y, Tail&&... tail)
328
{
329
#ifdef _GLIBCXX_DEBUG
330
	// Range can be a std::views::transform
331
	// but vector::insert in libstdc++ debug mode will wrongly try
332
	// to take its address to check for self-insertion (see
333
	// gcc/include/c++/7.1.0/debug/functions.h
334
	// __foreign_iterator_aux functions). So avoid vector::insert
335
	for (auto&& e : y) {
336
		x.emplace_back(std::forward<decltype(e)>(e));
337
	}
338
#else
339
	x.insert(std::end(x), std::begin(y), std::end(y));
340
#endif
341
	detail::append(x, std::forward<Tail>(tail)...);
342
}
343
344
// Allow move from an rvalue-vector.
345
// But don't allow to move from any rvalue-range. It breaks stuff like
346
//   append(v, std::views::reverse(w));
347
template<typename Result, typename T2, typename... Tail>
348
void append(Result& x, std::vector<T2>&& y, Tail&&... tail)
349
{
350
	x.insert(std::end(x),
351
		 std::move_iterator(std::begin(y)),
352
		 std::move_iterator(std::end(y)));
353
	detail::append(x, std::forward<Tail>(tail)...);
354
}
355
356
} // namespace detail
357
358
// Append a range to a vector.
359
template<typename T, typename... Tail>
360
void append(std::vector<T>& v, Tail&&... tail)
361
{
362
	auto extra = detail::sum_of_sizes(tail...);
363
	auto current = v.size();
364
	if (auto required = current + extra;
365
	    v.capacity() < required) {
366
		v.reserve(current + std::max(current, extra));
367
	}
368
	detail::append(v, std::forward<Tail>(tail)...);
369
}
370
371
// If both source and destination are vectors of the same type and the
372
// destination is empty and the source is an rvalue, then move the whole vector
373
// at once instead of moving element by element.
374
template<typename T>
375
void append(std::vector<T>& v, std::vector<T>&& range)
376
{
377
	if (v.empty()) {
378
		v = std::move(range);
379
	} else {
380
		v.insert(std::end(v),
381
		         std::move_iterator(std::begin(range)),
382
		         std::move_iterator(std::end(range)));
383
	}
384
}
385
386
template<typename T>
387
void append(std::vector<T>& x, std::initializer_list<T> list)
388
{
389
	x.insert(x.end(), list);
390
}
391
392
393
template<typename T = void, typename Range, typename... Tail>
394
[[nodiscard]] auto concat(const Range& range, Tail&&... tail)
395
{
396
	using T2 = detail::ToVectorType<T, decltype(std::begin(range))>;
397
	std::vector<T2> result;
398
	append(result, range, std::forward<Tail>(tail)...);
399
	return result;
400
}
401
402
template<typename T, typename... Tail>
403
[[nodiscard]] std::vector<T> concat(std::vector<T>&& v, Tail&&... tail)
404
{
405
	append(v, std::forward<Tail>(tail)...);
406
	return std::move(v);
407
}
408
409
410
template<typename T, size_t... Sizes>
411
constexpr auto concatArray(const std::array<T, Sizes>&... arrays)
412
{
413
	constexpr size_t totalSize = (0 + ... + Sizes);
414
	std::array<T, totalSize> result = {};
415
416
	size_t offset = 0;
417
	auto copy_array = [&]<size_t N>(const std::array<T, N>& arr) {
418
		for (size_t i = 0; i < N; ++i) result[offset + i] = arr[i];
419
		offset += N;
420
	};
421
422
	(copy_array(arrays), ...);
423
	return result;
424
}
425
426
// lookup in std::map
427
template<typename Key, typename Value, typename Comp, typename Key2>
428
[[nodiscard]] const Value* lookup(const std::map<Key, Value, Comp>& m, const Key2& k)
429
{
430
	auto it = m.find(k);
431
	return (it != m.end()) ? &it->second : nullptr;
432
}
433
434
template<typename Key, typename Value, typename Comp, typename Key2>
435
[[nodiscard]] Value* lookup(std::map<Key, Value, Comp>& m, const Key2& k)
436
{
437
	auto it = m.find(k);
438
	return (it != m.end()) ? &it->second : nullptr;
439
}
440
441
// will likely become part of future c++ standard
442
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
443
// explicit deduction guide (not needed as of C++20)
444
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
445
446
447
// --- Utility to retrieve the index for a given type from a std::variant ---
448
449
template<typename> struct get_index_tag {};
450
451
template<typename T, typename V> struct get_index;
452
453
template<typename T, typename... Ts>
454
struct get_index<T, std::variant<Ts...>>
455
    : std::integral_constant<size_t, std::variant<get_index_tag<Ts>...>(get_index_tag<T>()).index()> {};
456
457
458
// Utility to initialize an array via a generator function,
459
// without first default constructing the elements.
460
461
template<typename T, typename F, size_t... Is>
462
[[nodiscard]] static constexpr auto generate_array(F f, std::index_sequence<Is...>)
463
   -> std::array<T, sizeof...(Is)>
464
{
465
    return {{f(Is)...}};
466
}
467
468
template<size_t N, typename F>
469
[[nodiscard]] static constexpr auto generate_array(F f)
470
{
471
    using T = decltype(f(0));
472
    return generate_array<T>(f, std::make_index_sequence<N>{});
473
}
474
475
476
// Like std::array, but operator[] takes an enum
477
template<typename Enum, typename T, size_t S = size_t(-1)>
478
struct array_with_enum_index {
479
private:
480
	[[nodiscard]] static constexpr size_t get_size_helper() {
481
		if constexpr (requires { Enum::NUM; }) {
482
			return (S == size_t(-1)) ? static_cast<size_t>(Enum::NUM) : S;
483
		} else {
484
			assert(S != -1);
485
			return S;
486
		}
487
	}
488
489
public:
490
	std::array<T, get_size_helper()> storage;
491
492
	// Note: explicitly NO constructors, we want aggregate initialization
493
494
	[[nodiscard]] constexpr const auto& operator[](Enum e) const { return storage[std::to_underlying(e)]; }
495
	[[nodiscard]] constexpr auto& operator[](Enum e) { return storage[std::to_underlying(e)]; }
496
497
	// This list is incomplete. Add more when needed.
498
	[[nodiscard]] constexpr auto begin() const { return storage.begin(); }
499
	[[nodiscard]] constexpr auto begin() { return storage.begin(); }
500
	[[nodiscard]] constexpr auto end() const { return storage.end(); }
501
	[[nodiscard]] constexpr auto end() { return storage.end(); }
502
503
	[[nodiscard]] constexpr auto empty() const { return storage.empty(); }
504
	[[nodiscard]] constexpr auto size() const { return storage.size(); }
505
506
	[[nodiscard]] constexpr const auto* data() const { return storage.data(); }
507
	[[nodiscard]] constexpr auto* data() { return storage.data(); }
508
509
	[[nodiscard]] friend constexpr auto operator<=>(const array_with_enum_index& x, const array_with_enum_index& y) = default;
510
};
511
512
#endif