You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
7.4 KiB
C++

/*! \file
* \brief Templated \ref Queue for arbitrary objects.
*/
#pragma once
#include "../arch/core.h"
#include "../debug/assert.h"
#include "../object/outputstream.h"
#include "../types.h"
/*! \brief Templated Queue for arbitrary objects.
*
* Queue is implemented by a head-object (Queue<T>) and next-pointers embedded
* in the queued objects. By passing a different get_link function into the
* constructor, the member name of the next-pointer can be changed and objects
* can be contained in different independent queues.
*/
template <class T>
class Queue {
/// Default get_link implementation returns a pointer to the next-pointer.
static T** default_get_link(T& obj) { return &obj.queue_link; }
/// Type definition for the get_link function
typedef T** (*NextFunc)(T&);
/// Function pointer to the get_link function, called whenever the
/// next pointer array is referenced
const NextFunc get_link;
/// head points to the first element (the one returned on first dequeue).
/// Can be nullptr if the queue is empty.
T* head;
/// tail points to the last element (the one last added).
/// Is only valid if head != nullptr
T* tail;
// Prevent copies and assignments
Queue(const Queue&) = delete;
Queue& operator=(const Queue&) = delete;
public:
/*! \brief Minimal forward iterator
* You can use this iterator to iterate the queue like a normal STL container.
* It only supports forward iteration, since the queue is single linked.
*/
class Iterator {
private:
Queue<T>& queue;
T* current;
friend class Queue<T>;
Iterator(Queue<T>& queue, T* current) : queue(queue), current(current) {}
public:
Iterator operator+(unsigned num) {
if (current == nullptr) {
return *this;
}
T* temp = current;
while (num--) {
temp = queue.next(*temp);
}
return Iterator(queue, temp);
}
// pre increment
Iterator& operator++() {
current = queue.next(*current);
return *this;
}
// post increment
Iterator operator++(int) {
auto temp = Iterator(queue, current);
current = queue.next(*current);
return temp;
}
T* operator*() { return current; }
bool operator==(const Iterator& other) { return current == other.current; }
bool operator!=(const Iterator& other) { return !(*this == other); }
};
constexpr Queue(Queue&&) = default;
/*! \brief Constructor
* \param[in] get_link A function pointer to the get_link, i.e. a function
* which returns a pointer to the
*next-pointer of an element in the Queue.
*/
explicit Queue(NextFunc get_link = default_get_link)
: get_link(get_link), head(nullptr), tail(nullptr) {
assert(get_link != nullptr &&
"get_link function pointer must not be nullptr!");
};
/*! \brief Enqueues the provided item at the end of the queue. If the element
* is already contained in the queue, false will be returned
* \param[in] item element to be appended (enqueued).
* \return false if the element already was enqueued (and nothing was done)
* or not (and it is now enqueued, then true)
*/
bool enqueue(T& item) {
T** nextptr = get_link(item);
if (*nextptr != nullptr || (head != nullptr && tail == &item)) {
return false;
}
*nextptr = nullptr;
if (head == nullptr) {
head = tail = &item;
} else {
assert(tail != nullptr);
*get_link(*tail) = &item;
tail = &item;
}
return true;
}
/*! \brief insert a new element at the start of the queue
* \param[in] item the new item to add
* \return true if successful, false if item was already in the queue
**/
bool insertFirst(T& item) {
T** nextptr = get_link(item);
if (*nextptr != nullptr || (head != nullptr && tail == &item)) {
return false;
}
if (head == nullptr) {
tail = &item;
}
*nextptr = head;
head = &item;
return true;
}
/*! \brief Insert a new element item into the list after an element after.
* Returns false if item is already in the/a list or after is not in this
*list
* \param[in] after the element after which the new one should be inserted
* \param[in] item the new element to add
* \return true if successful, false if item was in the list or after was not
**/
bool insertAfter(T& after, T& item) {
// if queue is empty there is no after
// and tail is not valid so we need to check head here
if (head == nullptr) {
return false;
}
if (&after == tail) {
return enqueue(item);
}
T** nextptr = get_link(item);
// if item is already in the list return false
if (*nextptr != nullptr || tail == &item) {
return false;
}
T** pnextptr = get_link(after);
// if after is NOT in the list, return false
if (!(pnextptr != nullptr || tail == &after)) {
return false;
}
*nextptr = *pnextptr;
*pnextptr = &item;
return true;
}
/*! \brief return the next element of a given one or nullptr if the end is
*reached
* \param[in] item the current item
* \return the next element or nullptr if the end is reached or the item is
*not in this list
**/
T* next(T& item) {
T** nextptr = get_link(item);
if (head == nullptr || (*nextptr == nullptr && tail != &item)) {
return nullptr;
}
return *nextptr;
}
/*! \brief Return whether or not the queue is empty
* \return True if the queue is empty or false otherwise.
*/
bool is_empty() const { return (head == nullptr); }
/*! \brief Removes the first element in the queue and returns it.
* \note Does not update the tail-pointer
* \return Pointer to the removed item or `nullptr` if the queue was empty.
*/
T* dequeue() {
T* out = head;
if (head != nullptr) {
T** nextptr = get_link(*head);
head = *nextptr;
*nextptr = nullptr;
}
return out;
}
/*! \brief Removes a given element from the queue and returns that element,
* or nullptr if it was not present
* \return pointer to the removed element, or nullptr if not present
*/
T* remove(T* that) {
if (!that) return nullptr;
T* cur = head;
T** next_link;
if (head == that) {
head = *get_link(*head);
*get_link(*that) = nullptr;
return that;
}
while (cur) {
next_link = get_link(*cur);
if (*next_link == that) {
*next_link = *get_link(**next_link);
if (that == tail) {
tail = cur;
}
*get_link(*that) = nullptr;
return that;
}
cur = *next_link;
}
return nullptr;
}
/// get an iterator to the first element
Queue<T>::Iterator begin() { return Queue<T>::Iterator(*this, head); }
/// get an iterator that marks the end of list
Queue<T>::Iterator end() { return Queue<T>::Iterator(*this, nullptr); }
/// get the first element of the queue
T* first() { return head; }
/// get the last element of the queue
T* last() { return (head == nullptr ? nullptr : tail); }
};
/*! \brief Overload stream operator for list printing.
*
* With this a list can be printed. The elements itself are not printed, just
* the pointer.
*/
template <class T>
OutputStream& operator<<(OutputStream& os, Queue<T>& queue) {
os << "{";
for (typename Queue<T>::Iterator it = queue.begin(); it != queue.end();
++it) {
os << *it;
if (it + 1 != queue.end()) {
os << ", ";
}
}
return os << "}";
}