commons 0.1.5
Header-only C++23 library of common/shared types for the C++ libraries
Loading...
Searching...
No Matches
prioritized.hpp
Go to the documentation of this file.
1#pragma once
2
37
38#include <commons/types.hpp>
39
40#include <algorithm>
41#include <concepts>
42#include <cstddef>
43#include <functional>
44#include <initializer_list>
45#include <iterator>
46#include <limits>
47#include <memory>
48#include <set>
49#include <type_traits>
50#include <utility>
51
52// ---------------------------------------------------------------------------
53// Configurable sentinel values (override seam)
54//
55// These live here rather than in commons/config.hpp (which is reserved for the
56// boolean COMMONS_WITH_* integration gates) so the umbrella does not force
57// <limits> on every consumer. The static constexpr members of `Prioritized`
58// below are the canonical surface; these macros exist only so a build system or
59// consumer can predefine an override before this header is first included.
60// ---------------------------------------------------------------------------
61
62#if !defined(COMMONS_PRIORITIZED_HIGHEST_PRECEDENCE)
63#define COMMONS_PRIORITIZED_HIGHEST_PRECEDENCE (std::numeric_limits<int>::min())
64#endif
65#if !defined(COMMONS_PRIORITIZED_LOWEST_PRECEDENCE)
66#define COMMONS_PRIORITIZED_LOWEST_PRECEDENCE (std::numeric_limits<int>::max())
67#endif
68#if !defined(COMMONS_PRIORITIZED_DEFAULT_PRIORITY)
69#define COMMONS_PRIORITIZED_DEFAULT_PRIORITY 0
70#endif
71
72namespace comms {
73
74// ---------------------------------------------------------------------------
75// Prioritized — abstract interface (virtual-with-default, not pure)
76// ---------------------------------------------------------------------------
77
87 // The sentinels keep Spring's `Ordered` SCREAMING_CASE spelling rather than
88 // the project's lower_case constant style — it is the recognized convention
89 // for these specific values.
90 // NOLINTBEGIN(readability-identifier-naming)
97 // NOLINTEND(readability-identifier-naming)
98
99 virtual ~Prioritized() = default;
100
102 [[nodiscard]] virtual int priority() const noexcept {
103 return DEFAULT_PRIORITY;
104 }
105
106protected:
107 Prioritized() = default;
108 Prioritized(const Prioritized&) = default;
109 Prioritized(Prioritized&&) = default;
110 Prioritized& operator=(const Prioritized&) = default;
111 Prioritized& operator=(Prioritized&&) = default;
112};
113
116template <typename T>
117concept Prioritizable = requires(const T& t) {
118 { t.priority() } -> std::convertible_to<int>;
119};
120
121// ---------------------------------------------------------------------------
122// get_priority — uniform priority lookup
123// ---------------------------------------------------------------------------
124
125namespace detail {
126
128template <typename T>
129concept PointerReturningGet = requires(const T& t) {
130 { t.get() } -> std::convertible_to<const volatile void*>;
131};
132
137template <typename T>
138[[nodiscard]] inline int get_priority_ptr(const T* ptr) noexcept {
139 if (ptr == nullptr) {
141 }
142 if constexpr (Prioritizable<T>) {
143 return ptr->priority();
144 } else if constexpr (std::is_polymorphic_v<T>) {
145 if (const auto* p = dynamic_cast<const Prioritized*>(ptr)) {
146 return p->priority();
147 }
149 } else {
151 }
152}
153
154} // namespace detail
155
162template <typename T>
163 requires(!std::is_pointer_v<T>)
164[[nodiscard]] inline int get_priority(const T& value) noexcept {
165 if constexpr (std::is_base_of_v<Prioritized, T> || Prioritizable<T>) {
166 return value.priority();
167 } else if constexpr (detail::PointerReturningGet<T>) {
168 return detail::get_priority_ptr(value.get());
169 } else {
171 }
172}
173
176template <typename T>
177[[nodiscard]] inline int get_priority(const T* ptr) noexcept {
178 return detail::get_priority_ptr(ptr);
179}
180
181// ---------------------------------------------------------------------------
182// Comparators (strict-weak; lower value sorts first)
183// ---------------------------------------------------------------------------
184
189 [[nodiscard]] bool operator()(const std::shared_ptr<Prioritized>& a,
190 const std::shared_ptr<Prioritized>& b) const noexcept {
191 const int pa = a ? a->priority() : Prioritized::DEFAULT_PRIORITY;
192 if (const int pb = b ? b->priority() : Prioritized::DEFAULT_PRIORITY; pa != pb) {
193 return pa < pb;
194 }
195 return std::less<const Prioritized*>{}(a.get(), b.get());
196 }
197};
198
204template <typename T>
206 [[nodiscard]] bool operator()(const std::shared_ptr<T>& a,
207 const std::shared_ptr<T>& b) const noexcept {
208 const int pa = get_priority(a);
209 if (const int pb = get_priority(b); pa != pb) {
210 return pa < pb;
211 }
212 return std::less<const T*>{}(a.get(), b.get());
213 }
214};
215
216// ---------------------------------------------------------------------------
217// PrioritizedSet — a transparent std::set<T> with priority on top
218// ---------------------------------------------------------------------------
219
240template <typename T>
242 static_assert(std::equality_comparable<T>,
243 "PrioritizedSet<T> requires T to be equality-comparable");
244
247 struct Item {
248 T value;
249 int priority;
250 u64 order;
251 };
252
254 struct ItemCompare {
255 [[nodiscard]] bool operator()(const Item& a, const Item& b) const noexcept {
256 if (a.priority != b.priority) {
257 return a.priority < b.priority;
258 }
259 return a.order < b.order;
260 }
261 };
262
263 using set_type = std::set<Item, ItemCompare>;
264
265public:
266 using key_type = T;
267 using value_type = T;
268 using size_type = std::size_t;
269 using difference_type = std::ptrdiff_t;
270 using reference = const T&;
271 using const_reference = const T&;
272
276 // STL-style lower_case name (not the project's CamelCase) so the type reads
277 // as a standard container iterator at the call site.
278 // NOLINTNEXTLINE(readability-identifier-naming)
280 public:
281 using iterator_category = std::bidirectional_iterator_tag;
282 using value_type = T;
283 using difference_type = std::ptrdiff_t;
284 using pointer = const T*;
285 using reference = const T&;
286
287 const_iterator() = default;
288
289 [[nodiscard]] reference operator*() const noexcept {
290 return it_->value;
291 }
292 [[nodiscard]] pointer operator->() const noexcept {
293 return &it_->value;
294 }
295
296 const_iterator& operator++() noexcept {
297 ++it_;
298 return *this;
299 }
300 const_iterator operator++(int) noexcept {
301 const_iterator tmp = *this;
302 ++it_;
303 return tmp;
304 }
305 const_iterator& operator--() noexcept {
306 --it_;
307 return *this;
308 }
309 const_iterator operator--(int) noexcept {
310 const_iterator tmp = *this;
311 --it_;
312 return tmp;
313 }
314
315 [[nodiscard]] bool operator==(const const_iterator&) const = default;
316
317 private:
318 friend class PrioritizedSet;
319 explicit const_iterator(typename set_type::const_iterator it) noexcept : it_(it) {}
320 typename set_type::const_iterator it_{};
321 };
322
323 using iterator = const_iterator; // elements are immutable, like std::set keys
324 using reverse_iterator = std::reverse_iterator<iterator>;
325 using const_reverse_iterator = reverse_iterator;
326
327 PrioritizedSet() = default;
328
329 PrioritizedSet(std::initializer_list<T> init) {
330 for (const auto& v : init) {
331 insert(v);
332 }
333 }
334
335 template <typename It>
336 PrioritizedSet(It first, It last) {
337 for (; first != last; ++first) {
338 insert(*first);
339 }
340 }
341
342 // -- std::set-shaped, T-facing API ---------------------------------------
343
346 std::pair<iterator, bool> insert(const T& v) {
347 return insert(get_priority(v), v);
348 }
349
350 std::pair<iterator, bool> insert(T&& v) {
351 const int p = get_priority(v);
352 return insert(p, std::move(v));
353 }
354
358 std::pair<iterator, bool> insert(int priority, T v) {
359 if (const auto found = find_item(v); found != items_.end()) {
360 return {const_iterator{found}, false};
361 }
362 const auto [it, ok] = items_.insert(
363 Item{.value = std::move(v), .priority = priority, .order = next_order_++});
364 return {const_iterator{it}, ok};
365 }
366
368 template <typename... Args>
369 std::pair<iterator, bool> emplace(Args&&... args) {
370 return insert(T(std::forward<Args>(args)...));
371 }
372
373 void insert(std::initializer_list<T> init) {
374 for (const auto& v : init) {
375 insert(v);
376 }
377 }
378
379 template <typename It>
380 void insert(It first, It last) {
381 for (; first != last; ++first) {
382 insert(*first);
383 }
384 }
385
386 iterator erase(const_iterator pos) {
387 return const_iterator{items_.erase(pos.it_)};
388 }
389
390 iterator erase(const_iterator first, const_iterator last) {
391 return const_iterator{items_.erase(first.it_, last.it_)};
392 }
393
395 size_type erase(const T& v) {
396 const auto it = find_item(v);
397 if (it == items_.end()) {
398 return 0;
399 }
400 items_.erase(it);
401 return 1;
402 }
403
405 [[nodiscard]] iterator find(const T& v) const {
406 return const_iterator{find_item(v)};
407 }
408
410 [[nodiscard]] size_type count(const T& v) const {
411 return contains(v) ? 1U : 0U;
412 }
413
414 [[nodiscard]] bool contains(const T& v) const {
415 return find_item(v) != items_.end();
416 }
417
418 // -- priority-specific extras --------------------------------------------
419
421 [[nodiscard]] int priority_of(const T& v) const {
422 const auto it = find_item(v);
423 return it != items_.end() ? it->priority : Prioritized::DEFAULT_PRIORITY;
424 }
425
428 bool set_priority(const T& v, int priority) {
429 const auto it = find_item(v);
430 if (it == items_.end()) {
431 return false;
432 }
433 if (it->priority == priority) {
434 return true;
435 }
436 Item updated = *it; // preserves the original `order` tie-break
437 updated.priority = priority;
438 items_.erase(it);
439 items_.insert(std::move(updated));
440 return true;
441 }
442
443 [[nodiscard]] size_type size() const noexcept {
444 return items_.size();
445 }
446 [[nodiscard]] bool empty() const noexcept {
447 return items_.empty();
448 }
449
452 void clear() noexcept {
453 items_.clear();
454 }
455
456 void swap(PrioritizedSet& other) noexcept {
457 items_.swap(other.items_);
458 std::swap(next_order_, other.next_order_);
459 }
460
461 [[nodiscard]] iterator begin() const noexcept {
462 return const_iterator{items_.begin()};
463 }
464 [[nodiscard]] iterator end() const noexcept {
465 return const_iterator{items_.end()};
466 }
467 [[nodiscard]] iterator cbegin() const noexcept {
468 return begin();
469 }
470 [[nodiscard]] iterator cend() const noexcept {
471 return end();
472 }
473 [[nodiscard]] reverse_iterator rbegin() const noexcept {
474 return reverse_iterator{end()};
475 }
476 [[nodiscard]] reverse_iterator rend() const noexcept {
477 return reverse_iterator{begin()};
478 }
479 [[nodiscard]] const_reverse_iterator crbegin() const noexcept {
480 return rbegin();
481 }
482 [[nodiscard]] const_reverse_iterator crend() const noexcept {
483 return rend();
484 }
485
487 [[nodiscard]] friend bool operator==(const PrioritizedSet& a, const PrioritizedSet& b) {
488 return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
489 }
490
491private:
492 [[nodiscard]] typename set_type::const_iterator find_item(const T& v) const {
493 for (auto it = items_.begin(); it != items_.end(); ++it) {
494 if (it->value == v) {
495 return it;
496 }
497 }
498 return items_.end();
499 }
500
501 set_type items_;
502 u64 next_order_ = 0;
503};
504
505template <typename T>
506void swap(PrioritizedSet<T>& a, PrioritizedSet<T>& b) noexcept {
507 a.swap(b);
508}
509
510// ---------------------------------------------------------------------------
511// PrioritizedBuilder — CRTP mixin (mirrors FlagBuilderMixin)
512// ---------------------------------------------------------------------------
513
521template <typename Derived>
523public:
524 [[nodiscard]] int priority() const noexcept override {
525 return priority_;
526 }
527
529 Derived& priority(const int p) noexcept {
530 priority_ = p;
531 return self();
532 }
533
534 Derived& highest_priority() noexcept {
535 priority_ = HIGHEST_PRECEDENCE;
536 return self();
537 }
538
539 Derived& lowest_priority() noexcept {
540 priority_ = LOWEST_PRECEDENCE;
541 return self();
542 }
543
544protected:
545 int priority_ = Prioritized::DEFAULT_PRIORITY;
546
547private:
548 Derived& self() noexcept {
549 return static_cast<Derived&>(*this);
550 }
551};
552
553// ---------------------------------------------------------------------------
554// WithPriority — attach a priority to an existing value
555// ---------------------------------------------------------------------------
556
560template <typename T>
561inline constexpr bool with_priority_inherits = std::is_class_v<T> && !std::is_final_v<T>;
562
563namespace detail {
564
567template <typename T>
568class WithPriorityInherit : public T, public Prioritized {
569public:
570 using value_type = T;
571
572 template <typename... Args>
573 explicit WithPriorityInherit(const int p, Args&&... args)
574 : T(std::forward<Args>(args)...), priority_(p) {}
575
576 [[nodiscard]] int priority() const noexcept override {
577 return priority_;
578 }
579 void set_priority(const int p) noexcept {
580 priority_ = p;
581 }
582
583 [[nodiscard]] T& value() noexcept {
584 return static_cast<T&>(*this);
585 }
586 [[nodiscard]] const T& value() const noexcept {
587 return static_cast<const T&>(*this);
588 }
589
590protected:
591 int priority_ = Prioritized::DEFAULT_PRIORITY;
592};
593
596template <typename T>
597class WithPriorityCompose : public Prioritized {
598public:
599 using value_type = T;
600
601 template <typename... Args>
602 explicit WithPriorityCompose(const int p, Args&&... args)
603 : value_(std::forward<Args>(args)...), priority_(p) {}
604
605 [[nodiscard]] int priority() const noexcept override {
606 return priority_;
607 }
608 void set_priority(const int p) noexcept {
609 priority_ = p;
610 }
611
612 [[nodiscard]] T& value() noexcept {
613 return value_;
614 }
615 [[nodiscard]] const T& value() const noexcept {
616 return value_;
617 }
618
619 [[nodiscard]] T& operator*() noexcept {
620 return value_;
621 }
622 [[nodiscard]] const T& operator*() const noexcept {
623 return value_;
624 }
625 [[nodiscard]] T* operator->() noexcept {
626 return &value_;
627 }
628 [[nodiscard]] const T* operator->() const noexcept {
629 return &value_;
630 }
631
632protected:
633 T value_{};
634 int priority_ = Prioritized::DEFAULT_PRIORITY;
635};
636
637template <typename T>
638using WithPriorityBase =
639 std::conditional_t<with_priority_inherits<T>, WithPriorityInherit<T>, WithPriorityCompose<T>>;
640
641} // namespace detail
642
649template <typename T>
650class WithPriority : public detail::WithPriorityBase<T> {
651 using base = detail::WithPriorityBase<T>;
652
653public:
654 using value_type = T;
655 using base::base;
656
660 requires std::default_initializable<T>
661 : base(Prioritized::DEFAULT_PRIORITY) {}
662};
663
667template <typename T>
668[[nodiscard]] WithPriority<std::remove_cvref_t<T>> with_priority(int p, T&& item) {
669 return WithPriority<std::remove_cvref_t<T>>(p, std::forward<T>(item));
670}
671
674template <typename T, typename... Args>
675[[nodiscard]] std::shared_ptr<WithPriority<T>> make_prioritized(int p, Args&&... args) {
676 return std::make_shared<WithPriority<T>>(p, std::forward<Args>(args)...);
677}
678
679} // namespace comms
680
681// ---------------------------------------------------------------------------
682// Override seam macros (mirror flag.hpp's trailing declaration macros). These
683// only take effect if predefined before this header is first included; the
684// static constexpr members of `comms::Prioritized` are the canonical surface.
685// ---------------------------------------------------------------------------
686
CRTP mixin that makes Derived a Prioritized carrying a mutable priority, with fluent setters returnin...
Definition prioritized.hpp:522
int priority() const noexcept override
This object's priority; lower sorts first. Defaults to DEFAULT_PRIORITY.
Definition prioritized.hpp:524
Derived & priority(const int p) noexcept
Set the priority. Same-scope overload of the priority() getter above.
Definition prioritized.hpp:529
A thin bidirectional adapter over std::set<Item>::const_iterator that projects each Item to its const...
Definition prioritized.hpp:279
A set that behaves like std::set<T> from the outside — unique by T value, iterators yield const T&,...
Definition prioritized.hpp:241
bool set_priority(const T &v, int priority)
Re-snapshot v's priority and reorder it, keeping its insertion-order tie-break.
Definition prioritized.hpp:428
std::pair< iterator, bool > emplace(Args &&... args)
Construct a T from args and insert it at its discovered priority.
Definition prioritized.hpp:369
iterator find(const T &v) const
Linear lookup by value; end() when absent.
Definition prioritized.hpp:405
size_type count(const T &v) const
0 or 1 — elements are unique by value.
Definition prioritized.hpp:410
friend bool operator==(const PrioritizedSet &a, const PrioritizedSet &b)
Element-wise comparison over the ordered T sequence.
Definition prioritized.hpp:487
std::pair< iterator, bool > insert(const T &v)
Insert v at its discovered priority (get_priority(v), i.e.
Definition prioritized.hpp:346
size_type erase(const T &v)
Erase the element equal to v. Returns 0 or 1.
Definition prioritized.hpp:395
std::pair< iterator, bool > insert(int priority, T v)
Priority-aware addition.
Definition prioritized.hpp:358
void clear() noexcept
Remove all elements.
Definition prioritized.hpp:452
int priority_of(const T &v) const
The snapshotted priority of v, or DEFAULT_PRIORITY if absent.
Definition prioritized.hpp:421
A value of T carrying a priority.
Definition prioritized.hpp:650
WithPriority()
Default-constructs a T at DEFAULT_PRIORITY — available only when T is default-constructible (needed f...
Definition prioritized.hpp:659
Any type exposing a priority() convertible to int — satisfied by types deriving from Prioritized and ...
Definition prioritized.hpp:117
A type whose .get() yields something pointer-like (a smart pointer).
Definition prioritized.hpp:129
constexpr bool with_priority_inherits
Whether WithPriority<T> attaches its priority by inheritance (a true is-a T): only for non-final clas...
Definition prioritized.hpp:561
std::shared_ptr< WithPriority< T > > make_prioritized(int p, Args &&... args)
Make a shared_ptr<WithPriority<T>> (for use with the comparators / a std::set).
Definition prioritized.hpp:675
int get_priority_ptr(const T *ptr) noexcept
Priority of a pointee.
Definition prioritized.hpp:138
#define COMMONS_PRIORITIZED_HIGHEST_PRECEDENCE
Override for comms::Prioritized::HIGHEST_PRECEDENCE (default INT_MIN).
Definition prioritized.hpp:63
#define COMMONS_PRIORITIZED_LOWEST_PRECEDENCE
Override for comms::Prioritized::LOWEST_PRECEDENCE (default INT_MAX).
Definition prioritized.hpp:66
int get_priority(const T &value) noexcept
Priority of a value or reference.
Definition prioritized.hpp:164
#define COMMONS_PRIORITIZED_DEFAULT_PRIORITY
Override for comms::Prioritized::DEFAULT_PRIORITY (default 0).
Definition prioritized.hpp:69
WithPriority< std::remove_cvref_t< T > > with_priority(int p, T &&item)
Wrap item with priority p.
Definition prioritized.hpp:668
Like PrioritizedCompare, but over std::shared_ptr<T> for an arbitrary T that need not derive from Pri...
Definition prioritized.hpp:205
Orders std::shared_ptr<Prioritized> by ascending priority.
Definition prioritized.hpp:188
The orderable interface.
Definition prioritized.hpp:86
static constexpr int HIGHEST_PRECEDENCE
Most-preferred priority (INT_MIN): sorts before everything else.
Definition prioritized.hpp:92
static constexpr int DEFAULT_PRIORITY
The neutral priority (0) reported when none is set.
Definition prioritized.hpp:96
static constexpr int LOWEST_PRECEDENCE
Least-preferred priority (INT_MAX): sorts after everything else.
Definition prioritized.hpp:94
virtual int priority() const noexcept
This object's priority; lower sorts first. Defaults to DEFAULT_PRIORITY.
Definition prioritized.hpp:102
Fixed-width numeric aliases shared across the C++ libraries.
std::uint64_t u64
Unsigned 64-bit integer.
Definition types.hpp:25