Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
concurrency_utils.H
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
85#ifndef ALEPH_CONCURRENCY_UTILS_H
86#define ALEPH_CONCURRENCY_UTILS_H
87
88#include <atomic>
89#include <condition_variable>
90#include <concepts>
91#include <cstddef>
92#include <deque>
93#include <functional>
94#include <mutex>
95#include <optional>
96#include <shared_mutex>
97#include <stdexcept>
98#include <type_traits>
99#include <utility>
100#include <vector>
101
102#include <thread_pool.H>
103
104namespace Aleph
105{
140 template <typename T>
142 {
143 mutable std::mutex mutex_;
144 std::condition_variable not_empty_;
145 std::condition_variable not_full_;
146 std::deque<T> queue_;
147 size_t capacity_;
148 bool closed_ = false;
149
158 template <typename Predicate>
159 void wait_with_cancellation(std::condition_variable & cv,
160 std::unique_lock<std::mutex> & lock,
162 const CancellationToken & token)
163 {
164 if (pred())
165 return;
166 auto registration = token.notify_on_cancel(cv);
168 cv.wait(lock, [&] { return pred() or token.stop_requested(); });
169 if (not pred())
171 }
172
179 template <typename U>
180 bool send_impl(U && value)
181 {
182 std::unique_lock<std::mutex> lock(mutex_);
183 not_full_.wait(lock, [&] { return closed_ or queue_.size() < capacity_; });
184 if (closed_)
185 return false;
186 queue_.push_back(std::forward<U>(value));
187 lock.unlock();
188 not_empty_.notify_one();
189 return true;
190 }
191
200 template <typename U>
201 bool send_impl(U && value, const CancellationToken & token)
202 {
203 std::unique_lock<std::mutex> lock(mutex_);
205 [&] { return closed_ or queue_.size() < capacity_; },
206 token);
207 if (closed_)
208 return false;
209 queue_.push_back(std::forward<U>(value));
210 lock.unlock();
211 not_empty_.notify_one();
212 return true;
213 }
214
215 public:
222 explicit BoundedChannel(size_t capacity)
224 {
225 if (capacity_ == 0)
226 throw std::invalid_argument("BoundedChannel capacity must be greater than zero");
227 }
228
233
239 [[nodiscard]] size_t capacity() const noexcept { return capacity_; }
240
247 [[nodiscard]] bool is_closed() const
248 {
249 std::lock_guard<std::mutex> lock(mutex_);
250 return closed_;
251 }
252
259 [[nodiscard]] size_t size() const
260 {
261 std::lock_guard<std::mutex> lock(mutex_);
262 return queue_.size();
263 }
264
271 [[nodiscard]] bool empty() const
272 {
273 std::lock_guard<std::mutex> lock(mutex_);
274 return queue_.empty();
275 }
276
291 void close()
292 {
293 std::lock_guard<std::mutex> lock(mutex_);
294 if (closed_)
295 return;
296 closed_ = true;
297 not_empty_.notify_all();
298 not_full_.notify_all();
299 }
300
313 bool send(const T & value) { return send_impl(value); }
314
327 bool send(T && value) { return send_impl(std::move(value)); }
328
344 bool send(const T & value, const CancellationToken & token)
345 {
346 return send_impl(value, token);
347 }
348
364 bool send(T && value, const CancellationToken & token)
365 {
366 return send_impl(std::move(value), token);
367 }
368
380 template <typename... Args>
381 requires (sizeof...(Args) == 0)
382 bool emplace(Args && ... args)
383 {
384 std::unique_lock<std::mutex> lock(mutex_);
385 not_full_.wait(lock, [&] { return closed_ or queue_.size() < capacity_; });
386 if (closed_)
387 return false;
388 queue_.emplace_back(std::forward<Args>(args)...);
389 lock.unlock();
390 not_empty_.notify_one();
391 return true;
392 }
393
410 template <typename First, typename... Rest>
411 requires std::constructible_from<T, First, Rest...>
412 bool emplace(First && first, Rest && ... rest)
413 {
414 std::unique_lock<std::mutex> lock(mutex_);
415 not_full_.wait(lock, [&] { return closed_ or queue_.size() < capacity_; });
416 if (closed_)
417 return false;
418 queue_.emplace_back(std::forward<First>(first),
419 std::forward<Rest>(rest)...);
420 lock.unlock();
421 not_empty_.notify_one();
422 return true;
423 }
424
442 template <typename First, typename... Args>
443 requires std::same_as<std::remove_cvref_t<First>, CancellationToken> &&
444 (not std::constructible_from<T, First, Args...>) &&
445 std::constructible_from<T, Args...>
446 bool emplace(First && token, Args && ... args)
447 {
448 std::unique_lock<std::mutex> lock(mutex_);
450 [&] { return closed_ or queue_.size() < capacity_; },
451 token);
452 if (closed_)
453 return false;
454 queue_.emplace_back(std::forward<Args>(args)...);
455 lock.unlock();
456 not_empty_.notify_one();
457 return true;
458 }
459
469 bool try_send(const T & value)
470 {
471 std::lock_guard<std::mutex> lock(mutex_);
472 if (closed_ or queue_.size() >= capacity_)
473 return false;
474 queue_.push_back(value);
475 not_empty_.notify_one();
476 return true;
477 }
478
488 bool try_send(T && value)
489 {
490 std::lock_guard<std::mutex> lock(mutex_);
491 if (closed_ or queue_.size() >= capacity_)
492 return false;
493 queue_.push_back(std::move(value));
494 not_empty_.notify_one();
495 return true;
496 }
497
511 bool recv(T & out)
512 {
513 std::unique_lock<std::mutex> lock(mutex_);
514 not_empty_.wait(lock, [&] { return closed_ or not queue_.empty(); });
515 if (queue_.empty())
516 return false;
517 T value(std::move_if_noexcept(queue_.front()));
518 out = std::move(value);
519 queue_.pop_front();
520 lock.unlock();
521 not_full_.notify_one();
522 return true;
523 }
524
540 bool recv(T & out, const CancellationToken & token)
541 {
542 std::unique_lock<std::mutex> lock(mutex_);
544 [&] { return closed_ or not queue_.empty(); },
545 token);
546 if (queue_.empty())
547 return false;
548 T value(std::move_if_noexcept(queue_.front()));
549 out = std::move(value);
550 queue_.pop_front();
551 lock.unlock();
552 not_full_.notify_one();
553 return true;
554 }
555
568 [[nodiscard]] std::optional<T> recv()
569 {
570 std::unique_lock<std::mutex> lock(mutex_);
571 not_empty_.wait(lock, [&] { return closed_ or not queue_.empty(); });
572 if (queue_.empty())
573 return std::nullopt;
574 T value(std::move_if_noexcept(queue_.front()));
575 std::optional<T> result;
576 if constexpr (std::is_nothrow_move_constructible_v<T> or
577 not std::is_copy_constructible_v<T>)
578 result.emplace(std::move(value));
579 else
580 result.emplace(value);
581 queue_.pop_front();
582 lock.unlock();
583 not_full_.notify_one();
584 if constexpr (std::is_nothrow_move_constructible_v<T> or
585 not std::is_copy_constructible_v<T>)
586 return result;
587 else
588 return static_cast<const std::optional<T> &>(result);
589 }
590
606 [[nodiscard]] std::optional<T> recv(const CancellationToken & token)
607 {
608 std::unique_lock<std::mutex> lock(mutex_);
610 [&] { return closed_ or not queue_.empty(); },
611 token);
612 if (queue_.empty())
613 return std::nullopt;
614 T value(std::move_if_noexcept(queue_.front()));
615 std::optional<T> result;
616 if constexpr (std::is_nothrow_move_constructible_v<T> or
617 not std::is_copy_constructible_v<T>)
618 result.emplace(std::move(value));
619 else
620 result.emplace(value);
621 queue_.pop_front();
622 lock.unlock();
623 not_full_.notify_one();
624 if constexpr (std::is_nothrow_move_constructible_v<T> or
625 not std::is_copy_constructible_v<T>)
626 return result;
627 else
628 return static_cast<const std::optional<T> &>(result);
629 }
630
642 bool try_recv(T & out)
643 {
644 std::lock_guard<std::mutex> lock(mutex_);
645 if (queue_.empty())
646 return false;
647 T value(std::move_if_noexcept(queue_.front()));
648 out = std::move(value);
649 queue_.pop_front();
650 not_full_.notify_one();
651 return true;
652 }
653
664 [[nodiscard]] std::optional<T> try_recv()
665 {
666 std::lock_guard<std::mutex> lock(mutex_);
667 if (queue_.empty())
668 return std::nullopt;
669 T value(std::move_if_noexcept(queue_.front()));
670 std::optional<T> result;
671 if constexpr (std::is_nothrow_move_constructible_v<T> or
672 not std::is_copy_constructible_v<T>)
673 result.emplace(std::move(value));
674 else
675 result.emplace(value);
676 queue_.pop_front();
677 not_full_.notify_one();
678 if constexpr (std::is_nothrow_move_constructible_v<T> or
679 not std::is_copy_constructible_v<T>)
680 return result;
681 else
682 return static_cast<const std::optional<T> &>(result);
683 }
684 };
685
704 template <typename T, typename Mutex = std::mutex>
706 {
707 public:
713 {
714 std::unique_lock<Mutex> lock_;
716
717 public:
723 LockedPtr(Mutex & mutex, T & value)
724 : lock_(mutex), ptr_(&value) {}
725
730 [[nodiscard]] T & get() noexcept { return *ptr_; }
740 [[nodiscard]] T & operator * () noexcept { return *ptr_; }
741 };
742
748 {
749 std::unique_lock<Mutex> lock_;
750 const T *ptr_;
751
752 public:
758 ConstLockedPtr(Mutex & mutex, const T & value)
759 : lock_(mutex), ptr_(&value) {}
760
765 [[nodiscard]] const T & get() const noexcept { return *ptr_; }
770 [[nodiscard]] const T * operator -> () const noexcept { return ptr_; }
775 [[nodiscard]] const T & operator * () const noexcept { return *ptr_; }
776 };
777
778 private:
779 mutable Mutex mutex_;
781
782 public:
787
796
802 : value_(value) {}
803
808 explicit Synchronized(T && value)
809 : value_(std::move(value)) {}
810
816 template <typename... Args>
817 explicit Synchronized(std::in_place_t, Args && ... args)
818 : value_(std::forward<Args>(args)...) {}
819
827
836 {
838 }
839
855 template <typename F>
856 decltype(auto) with_lock(F && f)
857 {
858 using Result = std::invoke_result_t<F, T &>;
859 static_assert(not std::is_reference_v<Result>,
860 "Synchronized::with_lock callback must not return a reference");
861 std::unique_lock<Mutex> lock(mutex_);
862 return std::invoke(std::forward<F>(f), value_);
863 }
864
880 template <typename F>
881 decltype(auto) with_lock(F && f) const
882 {
883 using Result = std::invoke_result_t<F, const T &>;
884 static_assert(not std::is_reference_v<Result>,
885 "Synchronized::with_lock callback must not return a reference");
886 std::unique_lock<Mutex> lock(mutex_);
887 return std::invoke(std::forward<F>(f), std::as_const(value_));
888 }
889 };
890
904 template <typename T, typename SharedMutex = std::shared_mutex>
906 {
907 public:
912 {
913 std::shared_lock<SharedMutex> lock_;
914 const T *ptr_;
915
916 public:
922 ReadLockedPtr(SharedMutex & mutex, const T & value)
923 : lock_(mutex), ptr_(&value) {}
924
929 [[nodiscard]] const T & get() const noexcept { return *ptr_; }
934 [[nodiscard]] const T * operator -> () const noexcept { return ptr_; }
939 [[nodiscard]] const T & operator * () const noexcept { return *ptr_; }
940 };
941
946 {
947 std::unique_lock<SharedMutex> lock_;
949
950 public:
956 WriteLockedPtr(SharedMutex & mutex, T & value)
957 : lock_(mutex), ptr_(&value) {}
958
963 [[nodiscard]] T & get() noexcept { return *ptr_; }
973 [[nodiscard]] T & operator * () noexcept { return *ptr_; }
974 };
975
976 private:
979
980 public:
985
994
1000 : value_(value) {}
1001
1006 explicit RwSynchronized(T && value)
1007 : value_(std::move(value)) {}
1008
1013 template <typename... Args>
1014 explicit RwSynchronized(std::in_place_t, Args && ... args)
1015 : value_(std::forward<Args>(args)...) {}
1016
1023 {
1024 return ReadLockedPtr(mutex_, value_);
1025 }
1026
1035 {
1036 return WriteLockedPtr(mutex_, value_);
1037 }
1038
1050 template <typename F>
1051 decltype(auto) with_read_lock(F && f) const
1052 {
1053 using Result = std::invoke_result_t<F, const T &>;
1054 static_assert(not std::is_reference_v<Result>,
1055 "RwSynchronized::with_read_lock callback must not return a reference");
1056 std::shared_lock<SharedMutex> lock(mutex_);
1057 return std::invoke(std::forward<F>(f), std::as_const(value_));
1058 }
1059
1071 template <typename F>
1072 decltype(auto) with_write_lock(F && f)
1073 {
1074 using Result = std::invoke_result_t<F, T &>;
1075 static_assert(not std::is_reference_v<Result>,
1076 "RwSynchronized::with_write_lock callback must not return a reference");
1077 std::unique_lock<SharedMutex> lock(mutex_);
1078 return std::invoke(std::forward<F>(f), value_);
1079 }
1080 };
1081
1100 template <typename T>
1102 {
1103 std::vector<std::optional<T>> slots_;
1105 alignas(64) std::atomic<size_t> head_{0};
1106 alignas(64) std::atomic<size_t> tail_{0};
1107
1109 [[nodiscard]] size_t advance(size_t idx) const noexcept
1110 {
1111 return (idx + 1) % storage_size_;
1112 }
1113
1115 template <typename... Args>
1117 {
1118 const size_t tail = tail_.load(std::memory_order_relaxed);
1119 const size_t next = advance(tail);
1120 if (next == head_.load(std::memory_order_acquire))
1121 return false;
1122 slots_[tail].emplace(std::forward<Args>(args)...);
1123 tail_.store(next, std::memory_order_release);
1124 return true;
1125 }
1126
1127 public:
1134 explicit SpscQueue(size_t capacity)
1136 {
1137 if (capacity == 0)
1138 throw std::invalid_argument("SpscQueue capacity must be greater than zero");
1139 }
1140
1142 SpscQueue(const SpscQueue &) = delete;
1144 SpscQueue & operator = (const SpscQueue &) = delete;
1145
1152 [[nodiscard]] size_t capacity() const noexcept { return storage_size_ - 1; }
1153
1160 {
1161 return head_.load(std::memory_order_acquire) ==
1162 tail_.load(std::memory_order_acquire);
1163 }
1164
1171 {
1172 const size_t tail = tail_.load(std::memory_order_acquire);
1173 return advance(tail) == head_.load(std::memory_order_acquire);
1174 }
1175
1182 {
1183 const size_t head = head_.load(std::memory_order_acquire);
1184 const size_t tail = tail_.load(std::memory_order_acquire);
1185 if (tail >= head)
1186 return tail - head;
1187 return storage_size_ - head + tail;
1188 }
1189
1197 bool try_push(const T & value) { return emplace_impl(value); }
1198
1206 bool try_push(T && value) { return emplace_impl(std::move(value)); }
1207
1216 template <typename... Args>
1217 bool emplace(Args && ... args)
1218 {
1219 return emplace_impl(std::forward<Args>(args)...);
1220 }
1221
1230 bool try_pop(T & out)
1231 {
1232 const size_t head = head_.load(std::memory_order_relaxed);
1233 if (head == tail_.load(std::memory_order_acquire))
1234 return false;
1235 T value(std::move_if_noexcept(*slots_[head]));
1236 out = std::move(value);
1237 slots_[head].reset();
1238 head_.store(advance(head), std::memory_order_release);
1239 return true;
1240 }
1241
1251 [[nodiscard]] std::optional<T> try_pop()
1252 {
1253 const size_t head = head_.load(std::memory_order_relaxed);
1254 if (head == tail_.load(std::memory_order_acquire))
1255 return std::nullopt;
1256 T value(std::move_if_noexcept(*slots_[head]));
1257 std::optional<T> result;
1258 if constexpr (std::is_nothrow_move_constructible_v<T> or
1259 not std::is_copy_constructible_v<T>)
1260 result.emplace(std::move(value));
1261 else
1262 result.emplace(value);
1263 slots_[head].reset();
1264 head_.store(advance(head), std::memory_order_release);
1265 if constexpr (std::is_nothrow_move_constructible_v<T> or
1266 not std::is_copy_constructible_v<T>)
1267 return result;
1268 else
1269 return static_cast<const std::optional<T> &>(result);
1270 }
1271 };
1272
1278 template <typename T>
1280
1287 template <typename T, typename Mutex = std::mutex>
1289
1296 template <typename T, typename SharedMutex = std::shared_mutex>
1298
1304 template <typename T>
1306}
1307
1308#endif
Bounded blocking channel for producer-consumer workflows.
std::optional< T > recv(const CancellationToken &token)
Cancellation-aware receive of the next item from the channel.
bool emplace(Args &&... args)
Construct an element in-place at the back of the queue (no args).
size_t size() const
Return the number of currently queued items.
bool emplace(First &&token, Args &&... args)
Cancellation-aware in-place construction.
bool is_closed() const
Check whether the channel has been closed.
bool empty() const
Check whether the channel currently holds no queued items.
BoundedChannel(const BoundedChannel &)=delete
Deleted copy constructor.
size_t capacity() const noexcept
Return the maximum number of queued items.
bool recv(T &out)
Receive an item from the channel into out.
bool try_send(const T &value)
Attempt to send a value without blocking.
bool try_send(T &&value)
Attempt to send a value without blocking (move).
std::optional< T > try_recv()
Attempt to receive an item without blocking.
bool try_recv(T &out)
Attempt to receive an item into out without blocking.
bool send_impl(U &&value)
Common implementation for send operations.
bool send(const T &value, const CancellationToken &token)
Cancellation-aware blocking send (copy).
std::condition_variable not_empty_
void close()
Close the channel.
bool send(T &&value)
Send a value by moving it into the channel.
bool recv(T &out, const CancellationToken &token)
Cancellation-aware blocking receive into out.
std::condition_variable not_full_
BoundedChannel(size_t capacity)
Construct a channel with a fixed capacity.
bool emplace(First &&first, Rest &&... rest)
Construct an element in-place at the back of the queue.
bool send_impl(U &&value, const CancellationToken &token)
Common implementation for cancellation-aware send operations.
bool send(T &&value, const CancellationToken &token)
Cancellation-aware blocking send (move).
std::optional< T > recv()
Receive the next item from the channel.
void wait_with_cancellation(std::condition_variable &cv, std::unique_lock< std::mutex > &lock, Predicate pred, const CancellationToken &token)
Wait on a condition variable with cancellation support.
BoundedChannel & operator=(const BoundedChannel &)=delete
Deleted copy assignment operator.
bool send(const T &value)
Send a value by copying it into the channel.
Read-only cooperative cancellation token.
ConditionVariableRegistration notify_on_cancel(std::condition_variable &cv) const
Request notification of a condition variable on cancellation.
bool stop_requested() const noexcept
Return true if cancellation has been requested.
void throw_if_cancellation_requested() const
Throw operation_canceled if cancellation was requested.
RAII guard for shared (read-only) access.
std::shared_lock< SharedMutex > lock_
ReadLockedPtr(SharedMutex &mutex, const T &value)
Construct a guard and acquire a shared lock.
const T * operator->() const noexcept
Access members of the protected value.
const T & get() const noexcept
Access the protected value.
const T & operator*() const noexcept
Access the protected value.
RAII guard for exclusive (write) access.
T & operator*() noexcept
Access the protected value.
std::unique_lock< SharedMutex > lock_
WriteLockedPtr(SharedMutex &mutex, T &value)
Construct a guard and acquire an exclusive lock.
T * operator->() noexcept
Access members of the protected value.
T & get() noexcept
Access the protected value.
Read/write-lock protected shared object wrapper.
RwSynchronized()=default
Default-construct the protected value.
decltype(auto) with_read_lock(F &&f) const
Execute a callback with shared (read-only) access.
RwSynchronized(std::in_place_t, Args &&... args)
Construct the protected value in-place.
ReadLockedPtr read() const
Acquire a shared lock and return a guard.
WriteLockedPtr write()
Acquire an exclusive lock and return a guard.
decltype(auto) with_write_lock(F &&f)
Execute a callback with exclusive (write) access.
RwSynchronized(T &&value)
Construct by moving from value.
Bounded single-producer/single-consumer queue.
bool try_pop(T &out)
Attempt to pop an item from the queue into out.
std::atomic< size_t > head_
SpscQueue & operator=(const SpscQueue &)=delete
Deleted copy assignment operator.
bool empty() const noexcept
Check if the queue is empty.
SpscQueue(size_t capacity)
Construct a queue with the specified capacity.
bool emplace_impl(Args &&... args)
Internal implementation for push/emplace.
SpscQueue(const SpscQueue &)=delete
Deleted copy constructor.
size_t capacity() const noexcept
Return the queue's capacity.
bool try_push(T &&value)
Attempt to push an item (move) into the queue.
std::vector< std::optional< T > > slots_
bool full() const noexcept
Check if the queue is full.
size_t size() const noexcept
Return the number of items currently in the queue.
size_t advance(size_t idx) const noexcept
Compute the next index in the circular buffer.
bool emplace(Args &&... args)
Construct an item in-place at the tail of the queue.
bool try_push(const T &value)
Attempt to push an item (copy) into the queue.
std::optional< T > try_pop()
Attempt to pop an item from the queue.
std::atomic< size_t > tail_
RAII guard for read-only access to the synchronized value.
ConstLockedPtr(Mutex &mutex, const T &value)
Construct a guard and acquire the lock.
const T * operator->() const noexcept
Access members of the protected value.
const T & operator*() const noexcept
Access the protected value.
const T & get() const noexcept
Access the protected value.
RAII guard for exclusive access to the synchronized value.
LockedPtr(Mutex &mutex, T &value)
Construct a guard and acquire the lock.
T * operator->() noexcept
Access members of the protected value.
T & operator*() noexcept
Access the protected value.
std::unique_lock< Mutex > lock_
T & get() noexcept
Access the protected value.
Mutex-protected shared object wrapper.
decltype(auto) with_lock(F &&f)
Execute a callback with exclusive access to the value.
Synchronized()=default
Default-construct the protected value.
ConstLockedPtr lock() const
Acquire an exclusive lock (for const access) and return a guard.
Synchronized(std::in_place_t, Args &&... args)
Construct the protected value in-place.
decltype(auto) with_lock(F &&f) const
Execute a callback with read-only access to the value.
LockedPtr lock()
Acquire an exclusive lock and return a guard.
Synchronized(T &&value)
Construct by moving from value.
Freq_Node * pred
Predecessor node in level-order traversal.
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:105
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:171
STL namespace.
A modern, efficient thread pool for parallel task execution.