diff --git a/docs/index.adoc b/docs/index.adoc index e763c01..314c8d5 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -45,6 +45,7 @@ The following headers are available: * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/ct_string.hpp[`ct_string.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/function_traits.hpp[`function_traits.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/functional.hpp[`functional.hpp`] +* https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/intrusive_list.hpp[`instrusive_list.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/priority.hpp[`priority.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/type_traits.hpp[`type_traits.hpp`] * https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/utility.hpp[`utility.hpp`] @@ -239,6 +240,36 @@ v.emplace(0, stdx::with_result_of{make_S}); // this constructs S in-place thanks `with_result_of` can help to achieve in-place construction, effectively by deferring evaluation of function arguments. +== `intrusive_list.hpp` + +`intrusive_list` is a doubly-linked list designed for use at compile-time or +with static objects. It supports pushing and popping at the front or back, and +removal from the middle. + +[source,cpp] +---- +// A node in an intrusive_list must have prev and next pointers +struct node { + node *prev{}; + node *next{}; +}; + +stdx::intrusive_list l; + +node n1{}; +l.push_front(&n1); + +node n2{}; +l.push_back(&n2); + +node n3{}; +l.push_back(&n3); + +l.remove(&n2); // removal from the middle is constant-time +l.pop_front(); +l.pop_back(); +---- + == `priority.hpp` `priority_t` is a class that can be used for easily selecting complex diff --git a/include/stdx/intrusive_list.hpp b/include/stdx/intrusive_list.hpp new file mode 100644 index 0000000..45bfff9 --- /dev/null +++ b/include/stdx/intrusive_list.hpp @@ -0,0 +1,175 @@ +#pragma once + +#include + +#include +#include +#include + +namespace stdx { +inline namespace v1 { + +#if __cpp_concepts >= 201907L +namespace detail { +template +concept base_single_linkable = requires(T node) { + { node->next } -> same_as; +}; +} // namespace detail + +template +concept double_linkable = requires(T *node) { + requires detail::base_single_linkable< + std::remove_cvref_tnext)>>; + requires detail::base_single_linkable< + std::remove_cvref_tprev)>>; +}; + +#define STDX_DOUBLE_LINKABLE double_linkable +#else +#define STDX_DOUBLE_LINKABLE typename +#endif + +template class intrusive_list { + public: + struct iterator { + using difference_type = std::ptrdiff_t; + using value_type = NodeType; + using pointer = value_type *; + using reference = value_type &; + using iterator_category = std::forward_iterator_tag; + + constexpr iterator() = default; + constexpr explicit iterator(pointer n) : node{n} {} + + constexpr auto operator*() -> reference { return *node; } + constexpr auto operator*() const -> reference { return *node; } + constexpr auto operator->() -> pointer { return node; } + constexpr auto operator->() const -> pointer { return node; } + + constexpr auto operator++() -> iterator & { + node = node->next; + return *this; + } + constexpr auto operator++(int) -> iterator { + auto tmp = *this; + ++(*this); + return tmp; + } + + private: + pointer node{}; + +#if __cpp_impl_three_way_comparison < 201907L + friend constexpr auto operator==(iterator lhs, iterator rhs) -> bool { + return lhs.node == rhs.node; + } + friend constexpr auto operator!=(iterator lhs, iterator rhs) -> bool { + return not(lhs == rhs); + } +#else + friend constexpr auto operator==(iterator, iterator) -> bool = default; +#endif + }; + + using value_type = NodeType; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type &; + using const_reference = value_type const &; + using pointer = value_type *; + using const_pointer = value_type const *; + using const_iterator = iterator const; + + private: + pointer head{}; + pointer tail{}; + + public: + constexpr auto begin() -> iterator { return iterator{head}; } + constexpr auto begin() const -> const_iterator { return iterator{head}; } + constexpr auto cbegin() const -> const_iterator { return iterator{head}; } + constexpr auto end() -> iterator { return {}; } + constexpr auto end() const -> const_iterator { return {}; } + constexpr auto cend() -> const_iterator { return {}; } + + constexpr auto push_front(pointer n) -> void { + if (head != nullptr) { + head->prev = n; + } + n->next = head; + head = n; + n->prev = nullptr; + if (tail == nullptr) { + tail = n; + } + } + + constexpr auto push_back(pointer n) -> void { + if (tail != nullptr) { + tail->next = n; + } + n->prev = tail; + tail = n; + n->next = nullptr; + if (head == nullptr) { + head = n; + } + } + + constexpr auto pop_front() -> pointer { + pointer const poppedNode = head; + head = head->next; + + if (head == nullptr) { + tail = nullptr; + } else { + head->prev = nullptr; + } + + return poppedNode; + } + + constexpr auto pop_back() -> pointer { + pointer const poppedNode = tail; + tail = tail->prev; + + if (tail == nullptr) { + head = nullptr; + } else { + tail->next = nullptr; + } + + return poppedNode; + } + + [[nodiscard]] constexpr auto empty() const -> bool { + return head == nullptr; + } + + constexpr auto clear() -> void { + head = nullptr; + tail = nullptr; + } + + constexpr auto remove(pointer n) -> void { + pointer const nextNode = n->next; + pointer const prevNode = n->prev; + + if (prevNode == nullptr) { + head = nextNode; + } else { + prevNode->next = nextNode; + } + + if (nextNode == nullptr) { + tail = prevNode; + } else { + nextNode->prev = prevNode; + } + } +}; + +#undef STDX_DOUBLE_LINKABLE +} // namespace v1 +} // namespace stdx diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d515cdb..89e9cfd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -10,6 +10,7 @@ add_unit_test( conditional.cpp ct_string.cpp function_traits.cpp + intrusive_list.cpp overload.cpp priority.cpp remove_cvref.cpp diff --git a/test/intrusive_list.cpp b/test/intrusive_list.cpp new file mode 100644 index 0000000..65349c9 --- /dev/null +++ b/test/intrusive_list.cpp @@ -0,0 +1,289 @@ +#include + +#include + +namespace { +struct int_node { + int value{}; + int_node *prev{}; + int_node *next{}; +}; +#if __cpp_concepts >= 201907L +static_assert(stdx::double_linkable); +#endif + +struct double_link_node { + double_link_node *prev{}; + double_link_node *next{}; +}; + +struct bad_double_link_node { + int *prev{}; + int *next{}; +}; +} // namespace + +#if __cpp_concepts >= 201907L +TEST_CASE("double_linkable", "[intrusive_list]") { + static_assert(not stdx::double_linkable); + static_assert(not stdx::double_linkable); + static_assert(stdx::double_linkable); +} +#endif + +TEST_CASE("push_back, pop_front", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n{5}; + + list.push_back(&n); + auto poppedNode = list.pop_front(); + + CHECK(poppedNode->value == 5); +} + +TEST_CASE("push_front, pop_back", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n{5}; + + list.push_front(&n); + auto poppedNode = list.pop_back(); + + CHECK(poppedNode->value == 5); +} + +TEST_CASE("empty", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n{5}; + + CHECK(list.empty()); + + list.push_back(&n); + REQUIRE_FALSE(list.empty()); + + list.pop_front(); + CHECK(list.empty()); +} + +TEST_CASE("push_back 2, pop_front 2", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + + list.push_back(&n1); + list.push_back(&n2); + + CHECK(list.pop_front()->value == 1); + CHECK(list.pop_front()->value == 2); + CHECK(list.empty()); +} + +TEST_CASE("push_back 2, pop_front 2 (sequentially)", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + + list.push_back(&n1); + CHECK(list.pop_front()->value == 1); + CHECK(list.empty()); + + list.push_back(&n2); + CHECK(list.pop_front()->value == 2); + CHECK(list.empty()); +} + +TEST_CASE("push_back 2, pop_front 2 (multiple lists)", "[intrusive_list]") { + stdx::intrusive_list listA{}; + stdx::intrusive_list listB{}; + int_node n1{1}; + int_node n2{2}; + + listA.push_back(&n1); + listA.push_back(&n2); + listA.pop_front(); + listA.pop_front(); + CHECK(listA.empty()); + + listB.push_back(&n1); + CHECK(listB.pop_front()->value == 1); + CHECK(listB.empty()); +} + +TEST_CASE("remove middle node", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + int_node n3{3}; + + list.push_back(&n1); + list.push_back(&n2); + list.push_back(&n3); + + list.remove(&n2); + + CHECK(list.pop_front()->value == 1); + CHECK(list.pop_front()->value == 3); + CHECK(list.empty()); +} + +TEST_CASE("remove only node", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + + list.push_back(&n1); + + list.remove(&n1); + + CHECK(list.empty()); +} + +TEST_CASE("remove first node", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + + list.push_back(&n1); + list.push_back(&n2); + + list.remove(&n1); + + CHECK(list.pop_front()->value == 2); + CHECK(list.empty()); +} + +TEST_CASE("remove last node", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + + list.push_back(&n1); + list.push_back(&n2); + + list.remove(&n2); + + CHECK(list.pop_front()->value == 1); + CHECK(list.empty()); +} + +TEST_CASE("begin", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + int_node n3{3}; + + list.push_back(&n1); + list.push_back(&n2); + list.push_back(&n3); + + CHECK(std::begin(list)->value == 1); + CHECK(std::cbegin(list)->value == 1); +} + +TEST_CASE("iterator preincrement", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + int_node n3{3}; + + list.push_back(&n1); + list.push_back(&n2); + list.push_back(&n3); + + auto i = std::begin(list); + CHECK(i->value == 1); + + CHECK((++i)->value == 2); + CHECK(i->value == 2); + + CHECK((++i)->value == 3); + CHECK(i->value == 3); +} + +TEST_CASE("iterator postincrement", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + int_node n3{3}; + + list.push_back(&n1); + list.push_back(&n2); + list.push_back(&n3); + + auto i = std::begin(list); + CHECK(i->value == 1); + + CHECK((i++)->value == 1); + CHECK(i->value == 2); + + CHECK((i++)->value == 2); + CHECK(i->value == 3); +} + +TEST_CASE("iterator equality", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + + list.push_back(&n1); + + CHECK(std::begin(list) == std::begin(list)); +} + +TEST_CASE("iterator inequality", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + int_node n3{3}; + + list.push_back(&n1); + list.push_back(&n2); + list.push_back(&n3); + + auto i = std::begin(list); + CHECK(i == std::begin(list)); + CHECK(i != std::end(list)); + + i++; + CHECK(i != std::begin(list)); + CHECK(i != std::end(list)); + + i++; + CHECK(i != std::begin(list)); + CHECK(i != std::end(list)); + + i++; + CHECK(i != std::begin(list)); + CHECK(i == std::end(list)); +} + +TEST_CASE("clear", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + + list.push_back(&n1); + list.push_back(&n2); + list.clear(); + + CHECK(list.empty()); +} + +TEST_CASE("remove and re-add same node", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + int_node n2{2}; + + list.push_back(&n2); + list.push_back(&n1); + + list.remove(&n2); + list.push_back(&n2); + CHECK(list.pop_front()->value == 1); + CHECK(list.pop_front()->value == 2); + CHECK(list.empty()); +} + +TEST_CASE("remove from empty list", "[intrusive_list]") { + stdx::intrusive_list list{}; + int_node n1{1}; + list.remove(&n1); + CHECK(list.empty()); +}