Arene Base
Fundamental Utilities For Safety Critical C++
Loading...
Searching...
No Matches
priority_queue.hpp
Go to the documentation of this file.
1// Copyright 2024, Toyota Motor Corporation
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
5#ifndef INCLUDE_GUARD_ARENE_BASE_ARENE_BASE_INTRUSIVE_PRIORITY_QUEUE_HPP_
6#define INCLUDE_GUARD_ARENE_BASE_ARENE_BASE_INTRUSIVE_PRIORITY_QUEUE_HPP_
7
8// parasoft-begin-suppress AUTOSAR-A16_2_2-a "Arene Base aggregate headers permitted by A16-2-2 Permit #1"
9
10#include "arene/base/compiler_support/expect.hpp" // for check
11#include "arene/base/contracts.hpp"
12#include "arene/base/intrusive/binary_tree_node.hpp"
13#include "arene/base/intrusive/detail/default_tag.hpp"
14#include "arene/base/math.hpp"
15#include "arene/base/stdlib_choice/climits.hpp"
16#include "arene/base/stdlib_choice/cstddef.hpp"
17#include "arene/base/stdlib_choice/exchange.hpp"
18#include "arene/base/stdlib_choice/is_base_of.hpp"
19#include "arene/base/stdlib_choice/is_const.hpp"
20#include "arene/base/stdlib_choice/less.hpp"
21#include "arene/base/stdlib_choice/move.hpp"
22#include "arene/base/type_traits.hpp"
23#include "arene/base/utility/swap.hpp"
24
25// parasoft-end-suppress AUTOSAR-A16_2_2-a
26
27// parasoft-begin-suppress AUTOSAR-M2_10_1-a "Similar identifiers permitted by M2-10-1 Permit #1 v1.0.0"
28
29namespace arene {
30namespace base {
31namespace intrusive {
32
33/// @brief The intrusive priority_queue. By default, the @c priority_queue is
34/// building a max heap where the element at the top is always the largest.
35/// To build a min heap, pass a @c std::greater<> as the comparator.
36/// @tparam T The type of the element.
37/// @tparam Compare The type of the comparator. Default to @c std::less<>
38/// @tparam Tag The tag of the priority queue to work on. Default to @c
39/// default_tag.
40template <typename T, typename Compare = std::less<>, typename Tag = detail::default_tag>
42 /// @brief tree node type
44
45 static_assert(std::is_base_of<tree_node, T>::value, "Type T must be derived from binary_tree_node<Tag>");
46 static_assert(is_invocable_v<Compare, T const&, T const&>, "Compare must be invocable with two const T&");
47 static_assert(!std::is_const<T>::value, "T must not be const");
48
49 public:
50 /// @brief value comparison type
51 using value_compare = Compare;
52 /// @brief value type
53 using value_type = T;
54 /// @brief size type
56 /// @brief reference type
57 using reference = T&;
58 /// @brief const reference type
59 using const_reference = T const&;
60 /// @brief tag type
61 using tag = Tag;
62
63 /// @brief Construct an empty priority_queue
64 constexpr explicit priority_queue() noexcept = default;
65
66 // parasoft-begin-suppress AUTOSAR-A8_4_5-a "values are logically moved without use of std::move"
67 // parasoft-begin-suppress AUTOSAR-A8_4_6-a "False positive: no forwarding reference"
68 // parasoft-begin-suppress AUTOSAR-A12_8_4-a "False positive: in move constructor, copy initialization for data
69 // members of scalar types does not violate this rule"
70 /// @brief Move construct a @c priority_queue from another @c priority_queue.
71 /// The other @c priority_queue will be empty after the move.
72 /// @param other The other @c priority_queue
73 constexpr priority_queue(priority_queue&& other) noexcept
75 root_(std::exchange(other.root_, nullptr)) {}
76 // parasoft-end-suppress AUTOSAR-A12_8_4-a
77 // parasoft-end-suppress AUTOSAR-A8_4_6-a
78 // parasoft-end-suppress AUTOSAR-A8_4_5-a
79
80 /// @brief Move assign a @c priority_queue from another @c priority_queue.
81 /// The other @c priority_queue will be empty after the move.
82 /// @param other The other priority_queue
83 constexpr auto operator=(priority_queue&& other) noexcept -> priority_queue& {
84 if (this == &other) {
85 return *this;
86 }
87 priority_queue temp{std::move(other)};
88 swap(temp);
89 return *this;
90 }
91
92 // parasoft-begin-suppress CERT_C-EXP37-a-3 "False positive: The rule does not mention naming all parameters"
93 /// @brief Deleted copy constructor
94 ///
95 /// Intrusive priority queue does not support copy
96 ///
98 // parasoft-end-suppress CERT_C-EXP37-a-3
99
100 // parasoft-begin-suppress CERT_C-EXP37-a-3 "False positive: The rule does not mention naming all parameters"
101 /// @brief Deleted copy constructor
102 ///
103 /// Intrusive priority queue does not support copy
104 ///
105 auto operator=(priority_queue const&) -> priority_queue& = delete;
106 // parasoft-end-suppress CERT_C-EXP37-a-3
107
108 /// @brief Destructor
109 ~priority_queue() = default;
110
111 /// @brief Return a reference to the top element of the @c priority_queue.
112 /// @pre The queue must not be empty.
113 /// @return Reference to the top element.
114 constexpr auto top() const noexcept -> T& {
115 ARENE_PRECONDITION(!empty());
116 return static_cast<T&>(*root_);
117 }
118
119 /// @brief Return the number of elements in the @c priority_queue.
120 /// @return The number of elements in the @c priority_queue.
121 constexpr auto size() const noexcept -> size_type { return size_; }
122
123 /// @brief Check whether the @c priority_queue is empty.
124 /// @return True if the @c priority_queue is empty, false otherwise.
125 constexpr auto empty() const noexcept -> bool { return size() == size_type{}; }
126
127 /// @brief Push an element to the @c priority_queue.
128 /// @pre The element must not already be in a @c priority_queue.
129 /// @param value The element to be pushed.
130 constexpr void push(reference value) noexcept {
131 ARENE_PRECONDITION(!value.tree_node::is_linked());
132
133 if (empty()) {
134 root_ = static_cast<tree_node*>(&value);
135 parent(*root_) = root_; // parent_ points to itself for the root
136 size_ = 1U;
137 return;
138 }
139
140 // Push the node to the last position
141 // Find node to insert the new node, (size_ + 1) / 2
142 auto& parent_of_new_node = find_node_by_index((size_ + 1U) >> 1U);
143 if (!is_even(size_)) {
144 // Left
145 left(parent_of_new_node) = static_cast<tree_node*>(&value);
146 } else {
147 // Right
148 right(parent_of_new_node) = static_cast<tree_node*>(&value);
149 }
150 parent(value) = &parent_of_new_node;
151 ++size_;
152
153 // Now the node has been added to the last position,
154 // reheapify the priority_queue.
155 re_heapify(value);
156 }
157
158 /// @brief Pop the top element from the @c priority_queue.
159 /// @pre The @c priority_queue must not be empty.
160 constexpr void pop() noexcept {
161 ARENE_PRECONDITION(!empty());
162 erase_impl(*root_);
163 }
164
165 /// @brief Erase a specific node from the @c priority_queue.
166 /// @pre The node must be in the @c priority_queue.
167 /// @pre The @c priority_queue must not be empty.
168 /// @param node The node to be erased.
169 constexpr void erase(reference node) noexcept {
170 ARENE_PRECONDITION(!empty());
171 ARENE_PRECONDITION(contains(node));
172 erase_impl(node);
173 }
174
175 /// @brief Swap the contents of two priority queues.
176 /// @param other The queue to swap with.
177 constexpr void swap(priority_queue& other) noexcept {
178 using ::arene::base::swap; // constexpr eligible swap
179 swap(size_, other.size_);
180 swap(root_, other.root_);
181 }
182
183 // parasoft-begin-suppress CERT_C-EXP37-a "False positive: The rule does not mention naming all parameters"
184 /// @brief deleted equality operator
185 ///
186 /// Intrusive priority queue does not support equality comparison as the
187 /// definition of equality is not clear for intrusive containers.
188 ///
189 constexpr auto operator==(priority_queue const&) const -> bool = delete;
190 // parasoft-end-suppress CERT_C-EXP37-a
191
192 // parasoft-begin-suppress CERT_C-EXP37-a "False positive: The rule does not mention naming all parameters"
193 /// @brief deleted equality operator
194 ///
195 /// Intrusive priority queue does not support equality comparison as the
196 /// definition of equality is not clear for intrusive containers.
197 ///
198 constexpr auto operator!=(priority_queue const&) const -> bool = delete;
199 // parasoft-end-suppress CERT_C-EXP37-a
200
201 private:
202 /// @brief determine if an unsigned integer value is even
203 /// @param value value to check
204 /// @return @c true is value is even, otherwise @c false
205 static constexpr auto is_even(std::size_t value) noexcept -> bool { return (value & std::size_t{1U}) == 0U; }
206
207 /// @brief erase a node from the priority queue
208 /// @param node node to erase
209 /// @post nodes form a valid max/min heap
210 constexpr void erase_impl(tree_node& node) noexcept {
211 // If the queue only has one node, simply remove the root
212 if (size_ == 1U) {
213 parent(*root_) = nullptr;
214 root_ = nullptr;
215 --size_;
216 return;
217 }
218
219 auto& last_node = find_node_by_index(size_);
220 bool reheapify_needed{false};
221
222 if (static_cast<tree_node*>(&node) != &last_node) {
223 // Swap the node to be removed with the last node
224 swap_nodes(node, last_node); // last node become the node to be removed
225 reheapify_needed = true;
226 }
227
228 // Erase the last node
229 if (left(*parent(node)) == static_cast<tree_node*>(&node)) {
230 left(*parent(node)) = nullptr;
231 } else {
232 right(*parent(node)) = nullptr;
233 }
234 parent(node) = nullptr;
235 --size_;
236
237 if (reheapify_needed) {
238 re_heapify(last_node);
239 }
240 }
241
242 /// @brief Compare two nodes
243 /// @param node1 node to compare
244 /// @param node2 node to compare
245 /// @return @c comp_ of the value type of both nodes
246 ///
247 /// Compares two nodes using the stored comparison function object.
248 ///
249 constexpr auto compare_value(tree_node const& node1, tree_node const& node2) const noexcept -> bool {
250 return comp_(static_cast<T const&>(node1), static_cast<T const&>(node2));
251 }
252
253 /// @brief Move the node up until it reaches the correct position
254 /// @param node node to move
255 constexpr void move_node_up(tree_node& node) noexcept {
256 while ((&node != root_) && // Not root
257 compare_value(*parent(node), node)) {
258 if (left(*parent(node)) == &node) {
259 swap_with_left(*parent(node));
260 } else {
261 swap_with_right(*parent(node));
262 }
263 }
264 }
265
266 /// @brief Move the node down until it reaches the correct position
267 /// @param node node to move
268 constexpr void move_node_down(tree_node& node) noexcept {
269 while ((left(node) != nullptr) || (right(node) != nullptr)) {
270 bool should_swap_with_left{(left(node) != nullptr) && compare_value(node, *left(node))};
271 bool should_swap_with_right{(right(node) != nullptr) && compare_value(node, *right(node))};
272
273 if (should_swap_with_left && should_swap_with_right) {
274 should_swap_with_left = compare_value(*right(node), *left(node));
275 }
276
277 if (should_swap_with_left) {
278 swap_with_left(node);
279 } else if (should_swap_with_right) {
280 swap_with_right(node);
281 } else {
282 break;
283 }
284 }
285 }
286
287 /// @brief Re-heapify from a given node to form a valid max/min heap
288 /// by moving the node up and down till it reaches the correct position.
289 /// @param node The node to re-heapify.
290 constexpr void re_heapify(tree_node& node) noexcept {
291 move_node_up(node);
292 move_node_down(node);
293 }
294
295 /// @brief Find a node in the @c priority_queue based on its index.
296 /// @param n The index of the node to find. Index starts from 1, i.e.,
297 /// the root node has index 1. find_node_by_index(size_) returns the last
298 /// node.
299 /// @return Pointer to the found node.
300 constexpr auto find_node_by_index(std::size_t n) const noexcept -> tree_node& {
301 std::size_t const layers_num{log2(n)}; // Get the number of layers
302 auto* ptr = root_;
303 for (std::size_t i{1U}; i < layers_num + 1U; ++i) {
304 // parasoft-begin-suppress AUTOSAR-M5_8_1-a "The return value of 'log2' stored in
305 // 'layers_num' never exceeds 'sizeof(std::size_t)*CHAR_BIT - 1'.
306 // Loop counter 'i' is subtracted from that value.
307 // By construction of the loop, 'i' can never be larger than 'layers_num'.
308 // As a result, 'layers_num - i' is always bounded to the
309 // interval [0, sizeof(std::size_t)*CHAR_BIT - 1]"
310 ARENE_INVARIANT((layers_num - i) <= (sizeof(std::size_t) * std::size_t{CHAR_BIT} - 1U));
311
312 // Traverse down the tree, select the left or right
313 // child based on the bit of the index
314 if (is_even(n >> (layers_num - i))) {
315 ptr = left(*ptr);
316 } else {
317 ptr = right(*ptr);
318 }
319 // parasoft-end-suppress AUTOSAR-M5_8_1-a
320 }
321 ARENE_INVARIANT(ptr != nullptr);
322 return *ptr;
323 }
324
325 /// @brief Swap the given node with its left child.
326 /// @param node The node to swap.
327 constexpr void swap_with_left(tree_node& node) noexcept { swap_nodes(node, *left(node)); }
328
329 /// @brief Swap the given node with its right child.
330 /// @param node The node to swap.
331 constexpr void swap_with_right(tree_node& node) noexcept { swap_nodes(node, *right(node)); }
332
333 /// @brief Check if the @c priority_queue contains a specific node.
334 /// @param node The node to check.
335 /// @return True if the @c priority_queue contains the node, false otherwise
336 /// or if the node is @c nullptr.
337 constexpr auto contains(tree_node& node) const noexcept -> bool {
338 auto* traversed_root = &node;
339 while ((parent(*traversed_root) != nullptr) && (parent(*traversed_root) != traversed_root)) {
340 traversed_root = parent(*traversed_root);
341 }
342
343 return traversed_root == root_;
344 }
345
346 /// @brief Swap two nodes in the @c priority_queue. Given the current
347 /// algorithm, only the first node could possibly be root.
348 /// @param node_1 The first node to swap.
349 /// @param node_2 The second node to swap.
350 constexpr void swap_nodes(tree_node& node_1, tree_node& node_2) noexcept {
351 if ((root_ == &node_1) || (root_ == &node_2)) {
352 parent(*root_) = nullptr; // Temporarily change to simplify the algorithm
353 }
354
355 if (parent(node_1) != parent(node_2)) {
356 ::arene::base::swap(parent(node_1), parent(node_2));
357 // Update node_1's old parent to have node_2 as a child instead
358 // Justification: Clang-tidy complains update_child_of_parent seems to swap the
359 // two arguments, but it is not the case.
360 // NOLINTNEXTLINE(readability-suspicious-call-argument)
361 update_child_of_parent(node_2, node_1);
362 // Update node_2's old parent to have node_1 as a child instead
363 // NOLINTNEXTLINE(readability-suspicious-call-argument)
364 update_child_of_parent(node_1, node_2);
365 } else {
366 auto* parent_node_ptr = parent(node_1);
367 ARENE_INVARIANT(parent_node_ptr != nullptr);
368 auto& common_parent_node = *parent_node_ptr;
369 // Swap left/right child pointers of the common parent node
370 ::arene::base::swap(left(common_parent_node), right(common_parent_node));
371 }
372
373 // Swap child pointers
374 ::arene::base::swap(left(node_1), left(node_2));
375 ::arene::base::swap(right(node_1), right(node_2));
376
377 // Update children's parent pointers
378 update_parent_of_child(node_1);
379 update_parent_of_child(node_2);
380
381 // Update root if necessary
382 if (root_ == &node_1) {
383 root_ = &node_2;
384 parent(*root_) = root_;
385 }
386 }
387
388 /// @brief Update the child pointer of the parent of a @c node_1 to become
389 /// @c node_2
390 /// @param node_1 The previous child node.
391 /// @param node_2 The new child node.
392 static constexpr void update_child_of_parent(tree_node& node_1, tree_node& node_2) noexcept {
393 if (parent(node_1) != nullptr) {
394 if (left(*parent(node_1)) == &node_2) {
395 left(*parent(node_1)) = &node_1;
396 } else {
397 right(*parent(node_1)) = &node_1;
398 }
399 }
400 }
401
402 /// @brief Update the parent pointer of the children of a @c node_1 to
403 /// become
404 /// @c node
405 /// @param node The new parent node.
406 static constexpr void update_parent_of_child(tree_node& node) noexcept {
407 if (left(node) != nullptr) {
408 parent(*left(node)) = &node;
409 }
410 if (right(node) != nullptr) {
411 parent(*right(node)) = &node;
412 }
413 }
414
415 /// @brief Obtain the parent node
416 /// @param node node to obtain the parent of
417 /// @return reference to a pointer to the parent node
418 static constexpr auto parent(tree_node& node) noexcept -> tree_node*& {
419 return get_parent_link(node, Tag{}, detail::binary_tree_node_pass_key{});
420 }
421 /// @brief Obtain the left child node
422 /// @param node node to obtain the left child of
423 /// @return reference to a pointer to the left child node
424 static constexpr auto left(tree_node& node) noexcept -> tree_node*& {
425 return get_left_link(node, Tag{}, detail::binary_tree_node_pass_key{});
426 }
427 /// @brief Obtain the right child node
428 /// @param node node to obtain the right child of
429 /// @return reference to a pointer to the right child node
430 static constexpr auto right(tree_node& node) noexcept -> tree_node*& {
431 return get_right_link(node, Tag{}, detail::binary_tree_node_pass_key{});
432 }
433
434 // parasoft-begin-suppress AUTOSAR-A12_1_2-a "False positive: move constructors are exempt from this rule"
435 /// @brief node comparison function object
436 Compare comp_;
437 /// @brief number of node in the priority queue
438 std::size_t size_{};
439 /// @brief first node in the queue
440 tree_node* root_{nullptr};
441 // parasoft-end-suppress AUTOSAR-A12_1_2-a
442};
443} // namespace intrusive
444} // namespace base
445} // namespace arene
446#endif // INCLUDE_GUARD_ARENE_BASE_ARENE_BASE_INTRUSIVE_PRIORITY_QUEUE_HPP_
constexpr void swap(priority_queue &other) noexcept
Swap the contents of two priority queues.
Definition priority_queue.hpp:177
auto operator=(priority_queue const &) -> priority_queue &=delete
Deleted copy constructor.
constexpr auto size() const noexcept -> size_type
Return the number of elements in the priority_queue.
Definition priority_queue.hpp:121
Compare value_compare
value comparison type
Definition priority_queue.hpp:51
constexpr void erase(reference node) noexcept
Erase a specific node from the priority_queue.
Definition priority_queue.hpp:169
constexpr auto operator!=(priority_queue const &) const -> bool=delete
deleted equality operator
priority_queue(priority_queue const &)=delete
Deleted copy constructor.
constexpr priority_queue() noexcept=default
Construct an empty priority_queue.
constexpr auto top() const noexcept -> T &
Return a reference to the top element of the priority_queue.
Definition priority_queue.hpp:114
constexpr void pop() noexcept
Pop the top element from the priority_queue.
Definition priority_queue.hpp:160
constexpr auto empty() const noexcept -> bool
Check whether the priority_queue is empty.
Definition priority_queue.hpp:125
constexpr auto operator=(priority_queue &&other) noexcept -> priority_queue &
Move assign a priority_queue from another priority_queue. The other priority_queue will be empty afte...
Definition priority_queue.hpp:83
constexpr void push(reference value) noexcept
Push an element to the priority_queue.
Definition priority_queue.hpp:130
constexpr auto operator==(priority_queue const &) const -> bool=delete
deleted equality operator
constexpr priority_queue(priority_queue &&other) noexcept
Move construct a priority_queue from another priority_queue. The other priority_queue will be empty a...
Definition priority_queue.hpp:73
Definition binary_tree_node.hpp:14
Definition array_exceptions_disabled.cpp:11
Copyright 2026, Toyota Motor Corporation.
Definition array_exceptions_disabled.cpp:10