blob: decbda9c1941726206e3df3b30320d1eed042f71 [file] [log] [blame]
/** @file
Intrusive double linked list container.
This provides support for a doubly linked list container. Items in the list must provide links
inside the class and accessor functions for those links.
@note This is a header only library.
@section license License
Licensed to the Apache Software Foundation (ASF) under one or more contributor license
agreements. See the NOTICE file distributed with this work for additional information regarding
copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with the License. You may
obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
express or implied. See the License for the specific language governing permissions and
limitations under the License.
*/
#pragma once
/// Clang doesn't like just declaring the tag struct we need so we have to include the file.
#include <iterator>
#include <type_traits>
namespace ts
{
/** Intrusive doubly linked list container.
This holds items in a doubly linked list using links in the items. Items are placed in the list
by changing the pointers. An item can be in only one list for a set of links, but an item can
contain multiple sets of links. This requires different specializations of this template because
link access is part of the type specification. Memory for items is not managed by this class -
instances must be allocated and released elsewhere. In particular removing an item from the list
does not destruct or free the item.
Access to the links is described by a linkage class which is required to contain the following
members:
- The static method @c next_ptr which returns a reference to the pointer to the next item.
- The static method @c prev_ptr which returns a reference to the pointer to the previous item.
The pointer methods take a single argument of @c Item* and must return a reference to a pointer
instance. This type is deduced from the methods and is not explicitly specified. It must be
cheaply copyable and stateless.
It is the responsibility of the item class to initialize the link pointers. When an item is
removed from the list the link pointers are set to @c nullptr.
An example declaration would be
@code
// Item in the list.
struct Thing {
Thing* _next {nullptr};
Thing* _prev {nullptr};
Data _payload;
// Linkage descriptor.
struct Linkage {
static Thing*& next_ptr(Thing* Thing) { return Thing->_next; }
static Thing*& prev_ptr(Thing* Thing) { return Thing->_prev; }
};
};
using ThingList = ts::IntrusiveDList<Thing::Linkage>;
@endcode
Item access is done by using either STL style iteration, or direct access to the member
pointers. A client can have its own mechanism for getting an element to start, or use the @c
head and/or @c tail methods to get the first and last elements in the list respectively. Note if
the list is empty then @c nullptr will be returned. There are simple and fast conversions
between item pointers and iterators.
*/
template <typename L> class IntrusiveDList
{
friend class iterator;
public:
using self_type = IntrusiveDList; ///< Self reference type.
/// The list item type.
using value_type = typename std::remove_pointer<typename std::remove_reference<decltype(L::next_ptr(nullptr))>::type>::type;
/// Const iterator.
class const_iterator
{
using self_type = const_iterator; ///< Self reference type.
friend class IntrusiveDList;
public:
using list_type = IntrusiveDList; ///< Container type.
using value_type = const typename list_type::value_type; /// Import for API compliance.
// STL algorithm compliance.
using iterator_category = std::bidirectional_iterator_tag;
using pointer = value_type *;
using reference = value_type &;
using difference_type = int;
/// Default constructor.
const_iterator();
/// Pre-increment.
/// Move to the next element in the list.
/// @return The iterator.
self_type &operator++();
/// Pre-decrement.
/// Move to the previous element in the list.
/// @return The iterator.
self_type &operator--();
/// Post-increment.
/// Move to the next element in the list.
/// @return The iterator value before the increment.
self_type operator++(int);
/// Post-decrement.
/// Move to the previous element in the list.
/// @return The iterator value before the decrement.
self_type operator--(int);
/// Dereference.
/// @return A reference to the referent.
value_type &operator*() const;
/// Dereference.
/// @return A pointer to the referent.
value_type *operator->() const;
/// Convenience conversion to pointer type
/// Because of how this list is normally used, being able to pass an iterator as a pointer is quite convenient.
/// If the iterator isn't valid, it converts to @c nullptr.
operator value_type *() const;
/// Equality
bool operator==(self_type const &that) const;
/// Inequality
bool operator!=(self_type const &that) const;
protected:
// These are stored non-const to make implementing @c iterator easier. This class provides the required @c const
// protection.
list_type *_list{nullptr}; ///< Needed to decrement from @c end() position.
typename list_type::value_type *_v{nullptr}; ///< Referenced element.
/// Internal constructor for containers.
const_iterator(const list_type *list, value_type *v);
};
/// Iterator for the list.
class iterator : public const_iterator
{
using self_type = iterator; ///< Self reference type.
using super_type = const_iterator; ///< Super class type.
friend class IntrusiveDList;
public:
using list_type = IntrusiveDList; /// Must hoist this for direct use.
using value_type = typename list_type::value_type; /// Import for API compliance.
// STL algorithm compliance.
using iterator_category = std::bidirectional_iterator_tag;
using pointer = value_type *;
using reference = value_type &;
/// Default constructor.
iterator();
/// Pre-increment.
/// Move to the next element in the list.
/// @return The iterator.
self_type &operator++();
/// Pre-decrement.
/// Move to the previous element in the list.
/// @return The iterator.
self_type &operator--();
/// Post-increment.
/// Move to the next element in the list.
/// @return The iterator value before the increment.
self_type operator++(int);
/// Post-decrement.
/// Move to the previous element in the list.
/// @return The iterator value before the decrement.
self_type operator--(int);
/// Dereference.
/// @return A reference to the referent.
value_type &operator*() const;
/// Dereference.
/// @return A pointer to the referent.
value_type *operator->() const;
/// Convenience conversion to pointer type
/// Because of how this list is normally used, being able to pass an iterator as a pointer is quite convenient.
/// If the iterator isn't valid, it converts to @c nullptr.
operator value_type *() const;
protected:
/// Internal constructor for containers.
iterator(list_type *list, value_type *v);
};
/// Construct to empty list.
IntrusiveDList() = default;
/// Move list to @a this and leave @a that empty.
IntrusiveDList(self_type &&that);
/// No copy assignment because items can't be in two lists and can't copy items.
self_type &operator=(const self_type &that) = delete;
/// Move @a that to @a this.
self_type &operator=(self_type &&that);
/// Empty check.
/// @return @c true if the list is empty.
bool empty() const;
/// Presence check (linear time).
/// @return @c true if @a v is in the list, @c false if not.
bool contains(const value_type *v) const;
/// Add @a elt as the first element in the list.
/// @return This container.
self_type &prepend(value_type *v);
/// Add @elt as the last element in the list.
/// @return This container.
self_type &append(value_type *v);
/// Remove the first element of the list.
/// @return A pointer to the removed item, or @c nullptr if the list was empty.
value_type *take_head();
/// Remove the last element of the list.
/// @return A pointer to the removed item, or @c nullptr if the list was empty.
value_type *take_tail();
/// Insert a new element @a elt after @a target.
/// The caller is responsible for ensuring @a target is in this list and @a elt is not in a list.
/// @return This list.
self_type &insert_after(value_type *target, value_type *v);
/// Insert a new element @a v before @a target.
/// The caller is responsible for ensuring @a target is in this list and @a elt is not in a list.
/// @return This list.
self_type &insert_before(value_type *target, value_type *v);
/// Insert a new element @a elt after @a target.
/// If @a target is the end iterator, @a v is appended to the list.
/// @return This list.
self_type &insert_after(iterator const &target, value_type *v);
/// Insert a new element @a v before @a target.
/// If @a target is the end iterator, @a v is appended to the list.
/// @return This list.
self_type &insert_before(iterator const &target, value_type *v);
/// Take @a v out of this list.
/// @return The element after @a v.
value_type *erase(value_type *v);
/// Take the element at @a loc out of this list.
/// @return Iterator for the next element.
iterator erase(const iterator &loc);
/// Take elements out of the list.
/// Remove elements start with @a start up to but not including @a limit.
/// @return @a limit
iterator erase(const iterator &start, const iterator &limit);
/// Remove all elements.
/// @note @b No memory management is done!
/// @return This container.
self_type &clear();
/// @return Number of elements in the list.
size_t count() const;
/// Get an iterator to the first element.
iterator begin();
/// Get an iterator to the first element.
const_iterator begin() const;
/// Get an iterator past the last element.
iterator end();
/// Get an iterator past the last element.
const_iterator end() const;
/** Get an iterator for the item @a v.
*
* It is the responsibility of the caller that @a v is in the list. The purpose is to make
* iteration starting at a specific element easier (i.e. all of the link manipulation and checking
* is done by the iterator).
*
* @return An @c iterator that refers to @a v.
*/
iterator iterator_for(value_type *v);
const_iterator iterator_for(const value_type *v) const;
/// Get the first element.
value_type *head();
const value_type *head() const;
/// Get the last element.
value_type *tail();
const value_type *tail() const;
/** Apply a functor to every element in the list.
* This iterates over the list correctly even if the functor destroys or removes elements.
*/
template <typename F> self_type &apply(F &&f);
protected:
value_type *_head{nullptr}; ///< First element in list.
value_type *_tail{nullptr}; ///< Last element in list.
size_t _count{0}; ///< # of elements in list.
};
/** Utility class to provide intrusive links.
*
* @tparam T Class to link.
*
* The normal use is to declare this as a member to provide the links and the linkage functions.
* @code
* class Thing {
* // blah blah
* Thing* _next{nullptr};
* Thing* _prev{nullptr};
* using Linkage = ts::IntrusiveLinkage<Thing, &Thing::_next, &Thing::_prev>;
* };
* using ThingList = ts::ts::IntrusiveDList<Thing::Linkage>;
* @endcode
* The template will default to the names '_next' and '_prev' therefore in the example it could
* have been done as
* @code
* using Linkage = ts::IntrusiveLinkage<Thing>;
* @endcode
*/
template <typename T, T *(T::*NEXT) = &T::_next, T *(T::*PREV) = &T::_prev> struct IntrusiveLinkage {
static T *&next_ptr(T *thing); ///< Retrieve reference to next pointer.
static T *&prev_ptr(T *thing); ///< Retrieve reference to previous pointer.
};
template <typename T, T *(T::*NEXT), T *(T::*PREV)>
T *&
IntrusiveLinkage<T, NEXT, PREV>::next_ptr(T *thing)
{
return thing->*NEXT;
}
template <typename T, T *(T::*NEXT), T *(T::*PREV)>
T *&
IntrusiveLinkage<T, NEXT, PREV>::prev_ptr(T *thing)
{
return thing->*PREV;
}
/** Utility cast to change the underlying type of a pointer reference.
*
* @tparam T The resulting pointer reference type.
* @tparam P The starting pointer reference type.
* @param p A reference to pointer to @a P.
* @return A reference to the same pointer memory of type @c T*&.
*
* This changes a reference to a pointer to @a P to a reference to a pointer to @a T. This is useful
* for intrusive links that are inherited. For instance
*
* @code
* class Thing { Thing* _next; ... }
* class BetterThing : public Thing { ... };
* @endcode
*
* To make @c BetterThing work with an intrusive container without making new link members,
*
* @code
* static BetterThing*& next_ptr(BetterThing* bt) {
* return ts::ptr_ref_cast<BetterThing>(_next);
* }
* @endcode
*
* This is both convenient and gets around aliasing warnings from the compiler that can arise from
* using @c reinterpret_cast.
*/
template <typename T, typename P>
T *&
ptr_ref_cast(P *&p)
{
union {
P **_p;
T **_t;
} u{&p};
return *(u._t);
};
// --- Implementation ---
template <typename L> ts::IntrusiveDList<L>::const_iterator::const_iterator() {}
template <typename L>
ts::IntrusiveDList<L>::const_iterator::const_iterator(const list_type *list, value_type *v)
: _list(const_cast<list_type *>(list)), _v(const_cast<typename list_type::value_type *>(v))
{
}
template <typename L> ts::IntrusiveDList<L>::iterator::iterator() {}
template <typename L> ts::IntrusiveDList<L>::iterator::iterator(IntrusiveDList *list, value_type *v) : super_type(list, v) {}
template <typename L>
auto
ts::IntrusiveDList<L>::const_iterator::operator++() -> self_type &
{
_v = L::next_ptr(_v);
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::iterator::operator++() -> self_type &
{
this->super_type::operator++();
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::const_iterator::operator++(int) -> self_type
{
self_type tmp(*this);
++*this;
return tmp;
}
template <typename L>
auto
ts::IntrusiveDList<L>::iterator::operator++(int) -> self_type
{
self_type tmp(*this);
++*this;
return tmp;
}
template <typename L>
auto
ts::IntrusiveDList<L>::const_iterator::operator--() -> self_type &
{
if (_v) {
_v = L::prev_ptr(_v);
} else if (_list) {
_v = _list->_tail;
}
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::iterator::operator--() -> self_type &
{
this->super_type::operator--();
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::const_iterator::operator--(int) -> self_type
{
self_type tmp(*this);
--*this;
return tmp;
}
template <typename L>
auto
ts::IntrusiveDList<L>::iterator::operator--(int) -> self_type
{
self_type tmp(*this);
--*this;
return tmp;
}
template <typename L>
auto
ts::IntrusiveDList<L>::const_iterator::operator->() const -> value_type *
{
return _v;
}
template <typename L>
auto
ts::IntrusiveDList<L>::iterator::operator->() const -> value_type *
{
return super_type::_v;
}
template <typename L> ts::IntrusiveDList<L>::const_iterator::operator value_type *() const
{
return _v;
}
template <typename L>
auto
ts::IntrusiveDList<L>::const_iterator::operator*() const -> value_type &
{
return *_v;
}
template <typename L>
auto
ts::IntrusiveDList<L>::iterator::operator*() const -> value_type &
{
return *super_type::_v;
}
template <typename L> ts::IntrusiveDList<L>::iterator::operator value_type *() const
{
return super_type::_v;
}
/// --- Main class
template <typename L>
ts::IntrusiveDList<L>::IntrusiveDList(self_type &&that) : _head(that._head), _tail(that._tail), _count(that._count)
{
that.clear();
}
template <typename L>
bool
ts::IntrusiveDList<L>::empty() const
{
return _head == nullptr;
}
template <typename L>
bool
ts::IntrusiveDList<L>::contains(const value_type *v) const
{
for (auto thing = _head; thing; thing = L::next_ptr(thing)) {
if (thing == v)
return true;
}
return false;
}
template <typename L>
bool
ts::IntrusiveDList<L>::const_iterator::operator==(self_type const &that) const
{
return this->_v == that._v;
}
template <typename L>
bool
ts::IntrusiveDList<L>::const_iterator::operator!=(self_type const &that) const
{
return this->_v != that._v;
}
template <typename L>
auto
ts::IntrusiveDList<L>::prepend(value_type *v) -> self_type &
{
L::prev_ptr(v) = nullptr;
if (nullptr != (L::next_ptr(v) = _head)) {
L::prev_ptr(_head) = v;
} else {
_tail = v; // transition empty -> non-empty
}
_head = v;
++_count;
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::append(value_type *v) -> self_type &
{
L::next_ptr(v) = nullptr;
if (nullptr != (L::prev_ptr(v) = _tail)) {
L::next_ptr(_tail) = v;
} else {
_head = v; // transition empty -> non-empty
}
_tail = v;
++_count;
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::take_head() -> value_type *
{
value_type *zret = _head;
if (_head) {
if (nullptr == (_head = L::next_ptr(_head))) {
_tail = nullptr; // transition non-empty -> empty
} else {
L::prev_ptr(_head) = nullptr;
}
L::next_ptr(zret) = L::prev_ptr(zret) = nullptr;
--_count;
}
return zret;
}
template <typename L>
auto
ts::IntrusiveDList<L>::take_tail() -> value_type *
{
value_type *zret = _tail;
if (_tail) {
if (nullptr == (_tail = L::prev_ptr(_tail))) {
_head = nullptr; // transition non-empty -> empty
} else {
L::next_ptr(_tail) = nullptr;
}
L::next_ptr(zret) = L::prev_ptr(zret) = nullptr;
--_count;
}
return zret;
}
template <typename L>
auto
ts::IntrusiveDList<L>::insert_after(value_type *target, value_type *v) -> self_type &
{
if (target) {
if (nullptr != (L::next_ptr(v) = L::next_ptr(target))) {
L::prev_ptr(L::next_ptr(v)) = v;
} else if (_tail == target) {
_tail = v;
}
L::prev_ptr(v) = target;
L::next_ptr(target) = v;
++_count;
} else {
this->append(v);
}
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::insert_after(iterator const &target, value_type *v) -> self_type &
{
return this->insert_after(target._v, v);
}
template <typename L>
auto
ts::IntrusiveDList<L>::insert_before(value_type *target, value_type *v) -> self_type &
{
if (target) {
if (nullptr != (L::prev_ptr(v) = L::prev_ptr(target))) {
L::next_ptr(L::prev_ptr(v)) = v;
} else if (target == _head) {
_head = v;
}
L::next_ptr(v) = target;
L::prev_ptr(target) = v;
++_count;
} else {
this->append(v);
}
return *this;
}
template <typename L>
auto
ts::IntrusiveDList<L>::insert_before(iterator const &target, value_type *v) -> self_type &
{
return this->insert_before(target._v, v);
}
template <typename L>
auto
ts::IntrusiveDList<L>::erase(value_type *v) -> value_type *
{
value_type *zret{nullptr};
if (L::prev_ptr(v)) {
L::next_ptr(L::prev_ptr(v)) = L::next_ptr(v);
}
if (L::next_ptr(v)) {
zret = L::next_ptr(v);
L::prev_ptr(L::next_ptr(v)) = L::prev_ptr(v);
}
if (v == _head) {
_head = L::next_ptr(v);
}
if (v == _tail) {
_tail = L::prev_ptr(v);
}
L::prev_ptr(v) = L::next_ptr(v) = nullptr;
--_count;
return zret;
}
template <typename L>
auto
ts::IntrusiveDList<L>::erase(const iterator &loc) -> iterator
{
return this->iterator_for(this->erase(loc._v));
};
template <typename L>
auto
ts::IntrusiveDList<L>::erase(const iterator &first, const iterator &limit) -> iterator
{
value_type *spot = first;
value_type *prev{L::prev_ptr(spot)};
if (prev) {
L::next_ptr(prev) = limit;
}
if (spot == _head) {
_head = limit;
}
// tail is only updated if @a limit is @a end (e.g., @c nullptr).
if (nullptr == limit) {
_tail = prev;
} else {
L::prev_ptr(limit) = prev;
}
// Clear links in removed elements.
while (spot != limit) {
value_type *target{spot};
spot = L::next_ptr(spot);
L::prev_ptr(target) = L::next_ptr(target) = nullptr;
};
return {limit._v, this};
};
template <typename L>
auto
ts::IntrusiveDList<L>::operator=(self_type &&that) -> self_type &
{
if (this != &that) {
this->_head = that._head;
this->_tail = that._tail;
this->_count = that._count;
that.clear();
}
return *this;
}
template <typename L>
size_t
ts::IntrusiveDList<L>::count() const
{
return _count;
};
template <typename L>
auto
ts::IntrusiveDList<L>::begin() const -> const_iterator
{
return const_iterator{this, _head};
};
template <typename L>
auto
ts::IntrusiveDList<L>::begin() -> iterator
{
return iterator{this, _head};
};
template <typename L>
auto
ts::IntrusiveDList<L>::end() const -> const_iterator
{
return const_iterator{this, nullptr};
};
template <typename L>
auto
ts::IntrusiveDList<L>::end() -> iterator
{
return iterator{this, nullptr};
};
template <typename L>
auto
ts::IntrusiveDList<L>::iterator_for(value_type *v) -> iterator
{
return iterator{this, v};
};
template <typename L>
auto
ts::IntrusiveDList<L>::iterator_for(const value_type *v) const -> const_iterator
{
return const_iterator{this, v};
};
template <typename L>
auto
ts::IntrusiveDList<L>::tail() -> value_type *
{
return _tail;
}
template <typename L>
auto
ts::IntrusiveDList<L>::tail() const -> const value_type *
{
return _tail;
}
template <typename L>
auto
ts::IntrusiveDList<L>::head() -> value_type *
{
return _head;
}
template <typename L>
auto
ts::IntrusiveDList<L>::head() const -> const value_type *
{
return _head;
}
template <typename L>
auto
ts::IntrusiveDList<L>::clear() -> self_type &
{
_head = _tail = nullptr;
_count = 0;
return *this;
};
namespace detail
{
// Make @c apply more convenient by allowing the function to take a reference type or pointer type
// to the container elements. The pointer type is the base, plus a shim to convert from a reference
// type functor to a pointer pointer type. The complex return type definition forces only one, but
// not both, to be valid for a particular functor. This also must be done via free functions and not
// method overloads because the compiler forces a match up of method definitions and declarations
// before any template instantiation.
template <typename L, typename F>
auto
Intrusive_DList_Apply(ts::IntrusiveDList<L> &list, F &&f)
-> decltype(f(*static_cast<typename ts::IntrusiveDList<L>::value_type *>(nullptr)), list)
{
return list.apply([&f](typename ts::IntrusiveDList<L>::value_type *v) { return f(*v); });
}
template <typename L, typename F>
auto
Intrusive_DList_Apply(ts::IntrusiveDList<L> &list, F &&f)
-> decltype(f(static_cast<typename ts::IntrusiveDList<L>::value_type *>(nullptr)), list)
{
auto spot{list.begin()};
auto limit{list.end()};
while (spot != limit) {
f(spot++); // post increment means @a spot is updated before @a f is applied.
}
return list;
}
} // namespace detail
template <typename L>
template <typename F>
auto
ts::IntrusiveDList<L>::apply(F &&f) -> self_type &
{
return detail::Intrusive_DList_Apply(*this, f);
};
} // namespace ts