pw_containers: IntrusiveList updates

- Use an Item for List::head_ and have end() be &head_ instead of
  nullptr. This makes it possible to detect if an item is already in a
  list. Previously, checking nullptr was insufficient becuase the last
  item in the list pointed to nullptr. This approach also greatly
  simplifies the logic for adding and removing items.
- Add a destructor that calls clear().
- Allow constructing and assigning from iterators and initializer lists,
  and delete the copy constructor.
- Add erase_after and remove, which removes a specific item.
- Expand tests.

Change-Id: I392cdeca31e6dca5b3d29542d79636cfd996ca87
diff --git a/pw_containers/intrusive_list.cc b/pw_containers/intrusive_list.cc
index 20c6686..0ddfbe8 100644
--- a/pw_containers/intrusive_list.cc
+++ b/pw_containers/intrusive_list.cc
@@ -18,74 +18,44 @@
 
 namespace pw::intrusive_list_impl {
 
-void List::push_back(Item& item) {
-  // The incoming item's next is always nullptr, because item is added at the
-  // end of the list.
-  PW_CHECK_PTR_EQ(item.next_, nullptr);
-
-  if (head_ == nullptr) {
-    head_ = &item;
-    return;
-  }
-
-  Item* current = head_;
-
-  while (current->next_ != nullptr) {
-    current = current->next_;
-  }
-
-  current->next_ = &item;
-}
-
-List::Item& List::insert_after(Item* pos, Item& item) {
-  if (pos == nullptr) {
-    push_back(item);
-    return item;
-  }
-
-  // If `next_` of the incoming element is not null, the item is in use and
-  // can't be added to this list.
-  PW_CHECK_PTR_EQ(item.next_,
-                  nullptr,
-                  "Cannot add an item to a pw::IntrusiveList when it "
-                  "exists in another list");
+void List::insert_after(Item* pos, Item& item) {
+  PW_CHECK_PTR_EQ(
+      item.next_,
+      nullptr,
+      "Cannot add an item to a pw::IntrusiveList that is already in a list");
   item.next_ = pos->next_;
   pos->next_ = &item;
-  return item;
 }
 
-void List::push_front(Item& item) {
-  // If `next_` of the incoming element is not null, the item is in use and
-  // can't be added to this list.
-  PW_CHECK_PTR_EQ(item.next_,
-                  nullptr,
-                  "Cannot add an item to a pw::IntrusiveList when it "
-                  "exists in another list");
-  item.next_ = head_;
-  head_ = &item;
+void List::erase_after(Item* pos) {
+  Item* const item_to_remove = pos->next_;
+  pos->next_ = item_to_remove->next_;
+  item_to_remove->next_ = nullptr;
 }
 
-void List::pop_front() {
-  if (head_ == nullptr) {
-    return;
+List::Item* List::before_end() noexcept {
+  Item* pos = before_begin();
+  while (pos->next_ != end()) {
+    pos = pos->next_;
   }
-  Item* old_head = head_;
-  head_ = head_->next_;
-  old_head->next_ = nullptr;
+  return pos;
 }
 
 void List::clear() {
-  if (head_ == nullptr) {
-    return;
+  while (!empty()) {
+    erase_after(before_begin());
+  }
+}
+
+bool List::remove(const Item& item_to_remove) {
+  for (Item* pos = before_begin(); pos->next_ != end(); pos = pos->next_) {
+    if (pos->next_ == &item_to_remove) {
+      erase_after(pos);
+      return true;
+    }
   }
 
-  while (head_->next_ != nullptr) {
-    Item* item_to_remove = head_->next_;
-    head_->next_ = item_to_remove->next_;
-    item_to_remove->next_ = nullptr;
-  }
-
-  head_ = nullptr;
+  return false;
 }
 
 }  // namespace pw::intrusive_list_impl
diff --git a/pw_containers/intrusive_list_test.cc b/pw_containers/intrusive_list_test.cc
index 8f7dc5a..3d666e4 100644
--- a/pw_containers/intrusive_list_test.cc
+++ b/pw_containers/intrusive_list_test.cc
@@ -14,6 +14,7 @@
 
 #include "pw_containers/intrusive_list.h"
 
+#include <array>
 #include <cstddef>
 #include <cstdint>
 
@@ -27,13 +28,118 @@
  public:
   TestItem() : number_(0) {}
   TestItem(int number) : number_(number) {}
+
   int GetNumber() const { return number_; }
   void SetNumber(int num) { number_ = num; }
 
+  // Add equality comparison to ensure comparisons are done by identity rather
+  // than equality for the remove function.
+  bool operator==(const TestItem& other) const {
+    return number_ == other.number_;
+  }
+
  private:
   int number_;
 };
 
+TEST(IntrusiveList, Construct_InitializerList_Empty) {
+  IntrusiveList<TestItem> list({});
+  EXPECT_TRUE(list.empty());
+}
+
+TEST(IntrusiveList, Construct_InitializerList_One) {
+  TestItem one(1);
+  IntrusiveList<TestItem> list({&one});
+
+  EXPECT_EQ(&one, &list.front());
+}
+
+TEST(IntrusiveList, Construct_InitializerList_Multiple) {
+  TestItem one(1);
+  TestItem two(2);
+  TestItem thr(3);
+
+  IntrusiveList<TestItem> list({&one, &two, &thr});
+  auto it = list.begin();
+  EXPECT_EQ(&one, &(*it++));
+  EXPECT_EQ(&two, &(*it++));
+  EXPECT_EQ(&thr, &(*it++));
+  EXPECT_EQ(list.end(), it);
+}
+
+TEST(IntrusiveList, Construct_ObjectIterator_Empty) {
+  std::array<TestItem, 0> array;
+  IntrusiveList<TestItem> list(array.begin(), array.end());
+
+  EXPECT_TRUE(list.empty());
+}
+
+TEST(IntrusiveList, Construct_ObjectIterator_One) {
+  std::array<TestItem, 1> array{{{1}}};
+  IntrusiveList<TestItem> list(array.begin(), array.end());
+
+  EXPECT_EQ(&array.front(), &list.front());
+}
+
+TEST(IntrusiveList, Construct_ObjectIterator_Multiple) {
+  std::array<TestItem, 3> array{{{1}, {2}, {3}}};
+
+  IntrusiveList<TestItem> list(array.begin(), array.end());
+  auto it = list.begin();
+  EXPECT_EQ(&array[0], &(*it++));
+  EXPECT_EQ(&array[1], &(*it++));
+  EXPECT_EQ(&array[2], &(*it++));
+  EXPECT_EQ(list.end(), it);
+}
+
+TEST(IntrusiveList, Construct_PointerIterator_Empty) {
+  std::array<TestItem*, 0> array;
+  IntrusiveList<TestItem> list(array.begin(), array.end());
+
+  EXPECT_TRUE(list.empty());
+}
+
+TEST(IntrusiveList, Construct_PointerIterator_One) {
+  std::array<TestItem, 1> array{{{1}}};
+  std::array<TestItem*, 1> ptrs{{&array[0]}};
+
+  IntrusiveList<TestItem> list(ptrs.begin(), ptrs.end());
+
+  EXPECT_EQ(ptrs[0], &list.front());
+}
+
+TEST(IntrusiveList, Construct_PointerIterator_Multiple) {
+  std::array<TestItem, 3> array{{{1}, {2}, {3}}};
+  std::array<TestItem*, 3> ptrs{{&array[0], &array[1], &array[2]}};
+
+  IntrusiveList<TestItem> list(ptrs.begin(), ptrs.end());
+  auto it = list.begin();
+  EXPECT_EQ(ptrs[0], &(*it++));
+  EXPECT_EQ(ptrs[1], &(*it++));
+  EXPECT_EQ(ptrs[2], &(*it++));
+  EXPECT_EQ(list.end(), it);
+}
+
+TEST(IntrusiveList, Assign_ReplacesPriorContents) {
+  std::array<TestItem, 3> array{{{0}, {100}, {200}}};
+  IntrusiveList<TestItem> list(array.begin(), array.end());
+
+  list.assign(array.begin() + 1, array.begin() + 2);
+
+  auto it = list.begin();
+  EXPECT_EQ(&array[1], &(*it++));
+  EXPECT_EQ(list.end(), it);
+}
+
+TEST(IntrusiveList, Assign_EmptyRange) {
+  std::array<TestItem, 3> array{{{0}, {100}, {200}}};
+  IntrusiveList<TestItem> list(array.begin(), array.end());
+
+  list.assign(array.begin() + 1, array.begin() + 1);
+
+  EXPECT_TRUE(list.empty());
+}
+
 TEST(IntrusiveList, PushOne) {
   constexpr int kMagicValue = 31;
   IntrusiveList<TestItem> test_items;
@@ -216,8 +322,6 @@
 TEST(IntrusiveList, ListFront) {
   IntrusiveList<TestItem> test_items;
 
-  EXPECT_EQ(&test_items.front(), nullptr);
-
   TestItem item1(1);
   TestItem item2(0);
   TestItem item3(0xffff);
@@ -287,7 +391,131 @@
     it++;
   }
 }
+
 #endif  // NO_COMPILE_TESTS
 
+// TODO(pwbug/88): These tests should trigger a CHECK failure. This requires
+// using a testing version of pw_assert.
+#if TESTING_CHECK_FAILURES_IS_SUPPORTED
+
+TEST(IntrusiveList, Construct_DuplicateItems) {
+  TestItem item(1);
+  IntrusiveList<TestItem> list({&item, &item});
+}
+
+TEST(IntrusiveList, InsertAfter_SameItem) {
+  TestItem item(1);
+  IntrusiveList<TestItem> list({&item});
+
+  list.insert_after(list.begin(), item);
+}
+
+TEST(IntrusiveList, InsertAfter_SameItemAfterEnd) {
+  TestItem item(1);
+  IntrusiveList<TestItem> list({&item});
+
+  list.insert_after(list.end(), item);
+}
+
+TEST(IntrusiveList, PushBack_SameItem) {
+  TestItem item(1);
+  IntrusiveList<TestItem> list({&item});
+
+  list.push_back(item);
+}
+
+TEST(IntrusiveList, PushFront_SameItem) {
+  TestItem item(1);
+  IntrusiveList<TestItem> list({&item});
+
+  list.push_front(item);
+}
+
+#endif  // TESTING_CHECK_FAILURES_IS_SUPPORTED
+
+TEST(IntrusiveList, EraseAfter_FirstItem) {
+  std::array<TestItem, 3> items{{{0}, {1}, {2}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  auto it = list.erase_after(list.before_begin());
+  EXPECT_EQ(list.begin(), it);
+  EXPECT_EQ(&items[1], &list.front());
+}
+
+TEST(IntrusiveList, EraseAfter_LastItem) {
+  std::array<TestItem, 3> items{{{0}, {1}, {2}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  auto it = list.begin();
+  ++it;
+
+  it = list.erase_after(it);
+  EXPECT_EQ(list.end(), it);
+
+  it = list.begin();
+  ++it;
+
+  EXPECT_EQ(&items[1], &(*it));
+}
+
+TEST(IntrusiveList, EraseAfter_AllItems) {
+  std::array<TestItem, 3> items{{{0}, {1}, {2}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  list.erase_after(list.begin());
+  list.erase_after(list.begin());
+  auto it = list.erase_after(list.before_begin());
+
+  EXPECT_EQ(list.end(), it);
+  EXPECT_TRUE(list.empty());
+}
+
+TEST(IntrusiveList, Remove_EmptyList) {
+  std::array<TestItem, 1> items{{{3}}};
+  IntrusiveList<TestItem> list(items.begin(), items.begin());  // Add nothing!
+
+  EXPECT_TRUE(list.empty());
+  EXPECT_FALSE(list.remove(items[0]));
+}
+
+TEST(IntrusiveList, Remove_SingleItem_NotPresent) {
+  std::array<TestItem, 1> items{{{1}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  EXPECT_FALSE(list.remove(TestItem(1)));
+  EXPECT_EQ(&items.front(), &list.front());
+}
+
+TEST(IntrusiveList, Remove_SingleItem_Removed) {
+  std::array<TestItem, 1> items{{{1}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  EXPECT_TRUE(list.remove(items[0]));
+  EXPECT_TRUE(list.empty());
+}
+
+TEST(IntrusiveList, Remove_MultipleItems_NotPresent) {
+  std::array<TestItem, 5> items{{{1}, {1}, {2}, {3}, {4}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  EXPECT_FALSE(list.remove(TestItem(1)));
+}
+
+TEST(IntrusiveList, Remove_MultipleItems_RemoveAndPushBack) {
+  std::array<TestItem, 5> items{{{1}, {1}, {2}, {3}, {4}}};
+  IntrusiveList<TestItem> list(items.begin(), items.end());
+
+  EXPECT_TRUE(list.remove(items[0]));
+  EXPECT_TRUE(list.remove(items[3]));
+  list.push_back(items[0]);  // Make sure can add the item after removing it.
+
+  auto it = list.begin();
+  EXPECT_EQ(&items[1], &(*it++));
+  EXPECT_EQ(&items[2], &(*it++));
+  EXPECT_EQ(&items[4], &(*it++));
+  EXPECT_EQ(&items[0], &(*it++));
+  EXPECT_EQ(list.end(), it);
+}
+
 }  // namespace
 }  // namespace pw
diff --git a/pw_containers/public/pw_containers/internal/intrusive_list_impl.h b/pw_containers/public/pw_containers/internal/intrusive_list_impl.h
index 067187b..fe7e07d 100644
--- a/pw_containers/public/pw_containers/internal/intrusive_list_impl.h
+++ b/pw_containers/public/pw_containers/internal/intrusive_list_impl.h
@@ -15,15 +15,23 @@
 
 #include <iterator>
 
-namespace pw::intrusive_list_impl {
+namespace pw {
+
+template <typename>
+class IntrusiveList;
+
+namespace intrusive_list_impl {
 
 template <typename T, typename I>
 class Iterator {
  public:
+  using difference_type = void;
+  using value_type = std::remove_cv_t<T>;
+  using pointer = T*;
+  using reference = T&;
   using iterator_category = std::forward_iterator_tag;
 
   constexpr explicit Iterator() : item_(nullptr) {}
-  constexpr explicit Iterator(I* item) : item_{item} {}
 
   constexpr Iterator& operator++() {
     item_ = static_cast<I*>(item_->next_);
@@ -50,6 +58,12 @@
   }
 
  private:
+  template <typename>
+  friend class ::pw::IntrusiveList;
+
+  // Only allow IntrusiveList to create iterators that point to something.
+  constexpr explicit Iterator(I* item) : item_{item} {}
+
   I* item_;
 };
 
@@ -57,7 +71,7 @@
  public:
   class Item {
    protected:
-    constexpr Item() : next_(nullptr) {}
+    constexpr Item() : Item(nullptr) {}
 
    private:
     friend class List;
@@ -65,27 +79,76 @@
     template <typename T, typename I>
     friend class Iterator;
 
+    constexpr Item(Item* next) : next_(next) {}
+
     Item* next_;
   };
 
-  constexpr List() : head_(nullptr) {}
+  constexpr List() : head_(end()) {}
 
-  void push_back(Item& item);
+  template <typename Iterator>
+  List(Iterator first, Iterator last) : List() {
+    AssignFromIterator(first, last);
+  }
 
-  Item& insert_after(Item* pos, Item& item);
+  // Intrusive lists cannot be copied, since each Item can only be in one list.
+  List(const List&) = delete;
+  List& operator=(const List&) = delete;
 
-  void push_front(Item& item);
+  ~List() { clear(); }
 
-  void pop_front();
+  template <typename Iterator>
+  void assign(Iterator first, Iterator last) {
+    clear();
+    AssignFromIterator(first, last);
+  }
+
+  bool empty() const noexcept { return begin() == end(); }
+
+  static void insert_after(Item* pos, Item& item);
+
+  static void erase_after(Item* pos);
 
   void clear();
 
-  Item* begin() noexcept { return head_; }
-  const Item* begin() const noexcept { return head_; }
-  const Item* cbegin() const noexcept { return head_; }
+  bool remove(const Item& item_to_remove);
+
+  constexpr Item* before_begin() noexcept { return &head_; }
+  constexpr const Item* before_begin() const noexcept { return &head_; }
+
+  constexpr Item* begin() noexcept { return head_.next_; }
+  constexpr const Item* begin() const noexcept { return head_.next_; }
+
+  Item* before_end() noexcept;
+
+  constexpr Item* end() noexcept { return &head_; }
+  constexpr const Item* end() const noexcept { return &head_; }
 
  private:
-  Item* head_;
+  template <typename Iterator>
+  void AssignFromIterator(Iterator first, Iterator last);
+
+  // Use an Item for the head pointer. This gives simpler logic for inserting
+  // elements compared to using an Item*. It also makes it possible to use
+  // &head_ for end(), rather than nullptr. This makes end() unique for each
+  // List and ensures that items already in a list cannot be added to another.
+  Item head_;
 };
 
-}  // namespace pw::intrusive_list_impl
+template <typename Iterator>
+void List::AssignFromIterator(Iterator first, Iterator last) {
+  Item* current = &head_;
+
+  for (Iterator it = first; it != last; ++it) {
+    if constexpr (std::is_pointer<std::remove_reference_t<decltype(*it)>>()) {
+      insert_after(current, **it);
+      current = *it;
+    } else {
+      insert_after(current, *it);
+      current = &(*it);
+    }
+  }
+}
+
+}  // namespace intrusive_list_impl
+}  // namespace pw
diff --git a/pw_containers/public/pw_containers/intrusive_list.h b/pw_containers/public/pw_containers/intrusive_list.h
index a9a783a..cba6c8c 100644
--- a/pw_containers/public/pw_containers/intrusive_list.h
+++ b/pw_containers/public/pw_containers/intrusive_list.h
@@ -14,6 +14,7 @@
 #pragma once
 
 #include <cstddef>
+#include <initializer_list>
 #include <type_traits>
 
 #include "pw_containers/internal/intrusive_list_impl.h"
@@ -51,7 +52,7 @@
 class IntrusiveList {
  public:
   class Item : public intrusive_list_impl::List::Item {
-   public:
+   protected:
     constexpr Item() = default;
   };
 
@@ -63,35 +64,84 @@
   using const_iterator =
       intrusive_list_impl::Iterator<std::add_const_t<T>, const Item>;
 
-  [[nodiscard]] bool empty() const noexcept { return list_.begin() == nullptr; }
+  constexpr IntrusiveList() = default;
 
-  void push_back(T& item) { list_.push_back(item); }
+  // Constructs an IntrusiveList from an iterator over Items. The iterator may
+  // dereference as either Item& (e.g. from std::array<Item>) or Item* (e.g.
+  // from std::initializer_list<Item*>).
+  template <typename Iterator>
+  IntrusiveList(Iterator first, Iterator last) : list_(first, last) {}
 
-  void push_front(T& item) { list_.push_front(item); }
+  // Constructs an IntrusiveList from a std::initializer_list of pointers to
+  // items.
+  IntrusiveList(std::initializer_list<Item*> items)
+      : list_(items.begin(), items.end()) {}
 
-  iterator insert_after(iterator pos, T& item) {
-    return iterator(static_cast<Item*>(&list_.insert_after(&(*pos), item)));
+  template <typename Iterator>
+  void assign(Iterator first, Iterator last) {
+    list_.assign(first, last);
   }
 
-  void pop_front() { list_.pop_front(); }
+  void assign(std::initializer_list<Item*> items) {
+    list_.assign(items.begin(), items.end());
+  }
 
+  [[nodiscard]] bool empty() const noexcept { return list_.empty(); }
+
+  void push_front(T& item) { list_.insert_after(list_.before_begin(), item); }
+
+  void push_back(T& item) { list_.insert_after(list_.before_end(), item); }
+
+  iterator insert_after(iterator pos, T& item) {
+    list_.insert_after(&(*pos), item);
+    return iterator(&item);
+  }
+
+  // Removes the first item in the list. The list must not be empty.
+  void pop_front() { list_.erase_after(list_.before_begin()); }
+
+  // Removes the item following pos from the list. The item is not destructed.
+  iterator erase_after(iterator pos) {
+    list_.erase_after(&(*pos));
+    return ++pos;
+  }
+
+  // Removes all items from the list. The items themselves are not destructed.
   void clear() { list_.clear(); }
 
+  // Removes this specific item from the list, if it is present. Finds the item
+  // by identity (address comparison) rather than value equality. Returns true
+  // if the item was removed; false if it was not present.
+  bool remove(const T& item) { return list_.remove(item); }
+
+  // Reference to the first element in the list. Undefined behavior if empty().
   T& front() { return *static_cast<T*>(list_.begin()); }
 
+  // Reference to the last element in the list. Undefined behavior if empty().
+  T& back() { return *static_cast<T*>(list_.before_end()); }
+
+  // As in std::forward_list, returns the iterator before the begin() iterator.
+  iterator before_begin() noexcept {
+    return iterator(static_cast<Item*>(list_.before_begin()));
+  }
+  const_iterator before_begin() const noexcept {
+    return const_iterator(static_cast<const Item*>(list_.before_begin()));
+  }
+  const_iterator cbefore_begin() const noexcept { return before_begin(); }
+
   iterator begin() noexcept {
     return iterator(static_cast<Item*>(list_.begin()));
   }
   const_iterator begin() const noexcept {
-    return const_iterator(static_cast<const Item*>(list_.cbegin()));
+    return const_iterator(static_cast<const Item*>(list_.begin()));
   }
-  const_iterator cbegin() const noexcept {
-    return const_iterator(static_cast<const Item*>(list_.cbegin()));
-  }
+  const_iterator cbegin() const noexcept { return begin(); }
 
-  iterator end() noexcept { return iterator(); }
-  const_iterator end() const noexcept { return const_iterator(); }
-  const_iterator cend() const noexcept { return const_iterator(); }
+  iterator end() noexcept { return iterator(static_cast<Item*>(list_.end())); }
+  const_iterator end() const noexcept {
+    return const_iterator(static_cast<const Item*>(list_.end()));
+  }
+  const_iterator cend() const noexcept { return end(); }
 
  private:
   intrusive_list_impl::List list_;