pw_containers#

The pw_containers module provides embedded-friendly container classes.

pw::Vector#

The Vector class is similar to std::vector, except it is backed by a fixed-size buffer. Vectors must be declared with an explicit maximum size (e.g. Vector<int, 10>) but vectors can be used and referred to without the max size template parameter (e.g. Vector<int>).

To allow referring to a pw::Vector without an explicit maximum size, all Vector classes inherit from the generic Vector<T>, which stores the maximum size in a variable. This allows Vectors to be used without having to know their maximum size at compile time. It also keeps code size small since function implementations are shared for all maximum sizes.

pw::Vector<char>#

As a variable length type with a fixed-size buffer, Vector<char> makes a useful lightweight container for strings, avoiding the computation and bug-prone nature of null-termination, and overhead of pw::StringBuffer for long-term storage.

To facilitate this use, Vector<char> may be initialized from a const char* or std::string_view, and provides a view() method and conversion operator to obtain a std::string_view over the character data.

// Initialize from string.
pw::Vector<char, 32> greeting = "Hello";
// Initialize as part of an aggregate
struct Person {
  pw::Vector<char, 32> name;
  int age;
};

Person bob = {
  .name = "Bob",
  .age = 42;
};
// Initialize using std::string_view conversion from pw::StringBuffer
pw::StringBuffer<150> buffer;
buffer << "Hello";
pw::Vector<char, 32> greeting = buffer;

// Or use conversion to std::string_view to add to a buffer.
buffer << " " << person.name;

Since the internal array is not null-terminated, to use the string in code that expects that, pw_string provides a version of pw::string::Copy that takes a vector as a source.

#include "pw_containers/vector.h"
#include "pw_string/vector.h"

pw::Vector<char, 32> greeting = "Hello";
char c_str[33];

pw::string::Copy(greeting, c_str);
printf("%s\n", greeting);

pw::IntrusiveList#

IntrusiveList provides an embedded-friendly singly-linked intrusive list implementation. An intrusive list is a type of linked list that embeds the “next” pointer into the list object itself. This allows the construction of a linked list without the need to dynamically allocate list entries.

In C, an intrusive list can be made by manually including the “next” pointer as a member of the object’s struct. pw::IntrusiveList uses C++ features to simplify the process of creating an intrusive list. pw::IntrusiveList provides a class that list elements can inherit from. This protects the “next” pointer from being accessed by the item class, so only the pw::IntrusiveList class can modify the list.

Usage#

While the API of pw::IntrusiveList is similar to a std::forward_list, there are extra steps to creating objects that can be stored in this data structure. Objects that will be added to a IntrusiveList<T> must inherit from IntrusiveList<T>::Item. They can inherit directly from it or inherit from it through another base class. When an item is instantiated and added to a linked list, the pointer to the object is added to the “next” pointer of whichever object is the current tail.

That means two key things:

  • An instantiated IntrusiveList<T>::Item will be removed from its corresponding IntrusiveList when it goes out of scope.

  • A linked list item CANNOT be included in two lists. Attempting to do so results in an assert failure.

class Square
   : public pw::IntrusiveList<Square>::Item {
 public:
  Square(unsigned int side_length) : side_length(side_length) {}
  unsigned long Area() { return side_length * side_length; }

 private:
  unsigned int side_length;
};

pw::IntrusiveList<Square> squares;

Square small(1);
Square large(4000);
// These elements are not copied into the linked list, the original objects
// are just chained together and can be accessed via
// `IntrusiveList<Square> squares`.
squares.push_back(small);
squares.push_back(large);

{
  // When different_scope goes out of scope, it removes itself from the list.
  Square different_scope = Square(5);
  squares.push_back(&different_scope);
}

for (const auto& square : squares) {
  PW_LOG_INFO("Found a square with an area of %lu", square.Area());
}

// Like std::forward_list, an iterator is invalidated when the item it refers
// to is removed. It is *NOT* safe to remove items from a list while iterating
// over it in a range-based for loop.
for (const auto& square_bad_example : squares) {
  if (square_bad_example.verticies() != 4) {
    // BAD EXAMPLE of how to remove matching items from a singly linked list.
    squares.remove(square_bad_example);  // NEVER DO THIS! THIS IS A BUG!
  }
}

// To remove items while iterating, use an iterator to the previous item.
auto previous = squares.before_begin();
auto current = squares.begin();

while (current != squares.end()) {
  if (current->verticies() != 4) {
    current = squares.erase_after(previous);
  } else {
    previous = current;
    ++current;
  }
}

pw::containers::FlatMap#

FlatMap provides a simple, fixed-size associative array with lookup by key or value. pw::containers::FlatMap contains the same methods and features for looking up data as std::map. However, there are no methods that modify the underlying data. The underlying array in pw::containers::FlatMap does not need to be sorted. During construction, pw::containers::FlatMap will perform a constexpr insertion sort.

pw::containers::FilteredView#

pw::containers::FilteredView provides a view of a container that only contains elements that match the specified filter. This class is similar to C++20’s std::ranges::filter_view.

To create a FilteredView, pass a container and a filter object, which may be a lambda or class that implements operator() for the container’s value type.

std::array<int, 99> kNumbers = {3, 1, 4, 1, ...};

for (int even : FilteredView(kNumbers, [](int n) { return n % 2 == 0; })) {
  PW_LOG_INFO("This number is even: %d", even);
}

pw::containers::WrappedIterator#

pw::containers::WrappedIterator is a class that makes it easy to wrap an existing iterator type. It reduces boilerplate by providing operator++, operator--, operator==, operator!=, and the standard iterator aliases (difference_type, value_type, etc.). It does not provide the dereference operator; that must be supplied by a derived class.

To use it, create a class that derives from WrappedIterator and define operator*() and operator->() as appropriate. The new iterator might apply a transformation to or access a member of the values provided by the original iterator. The following example defines an iterator that multiplies the values in an array by 2.

// Divides values in a std::array by two.
class DoubleIterator
    : public pw::containers::WrappedIterator<DoubleIterator, const int*, int> {
 public:
  constexpr DoubleIterator(const int* it) : WrappedIterator(it) {}

  int operator*() const { return value() * 2; }

  // Don't define operator-> since this iterator returns by value.
};

constexpr std::array<int, 6> kArray{0, 1, 2, 3, 4, 5};

void SomeFunction {
  for (DoubleIterator it(kArray.begin()); it != DoubleIterator(kArray.end()); ++it) {
    // The iterator yields 0, 2, 4, 6, 8, 10 instead of the original values.
  }
};

WrappedIterator may be used in concert with FilteredView to create a view that iterates over a matching values in a container and applies a transformation to the values. For example, it could be used with FilteredView to filter a list of packets and yield only one field from the packet.

The combination of FilteredView and WrappedIterator provides some basic functional programming features similar to (though much more cumbersome than) generator expressions (or filter/map) in Python or streams in Java 8. WrappedIterator and FilteredView require no memory allocation, which is helpful when memory is too constrained to process the items into a new container.

pw::containers::to_array#

pw::containers::to_array is a C++14-compatible implementation of C++20’s std::to_array. In C++20, it is an alias for std::to_array. It converts a C array to a std::array.

pw_containers/algorithm.h#

Pigweed provides a set of Container-based versions of algorithmic functions within the C++ standard library, based on a subset of absl/algorithm/container.h.

bool pw::containers::AllOf()#

Container-based version of the <algorithm> std::all_of() function to test if all elements within a container satisfy a condition.

bool pw::containers::AnyOf()#

Container-based version of the <algorithm> std::any_of() function to test if any element in a container fulfills a condition.

bool pw::containers::NoneOf()#

Container-based version of the <algorithm> std::none_of() function to test if no elements in a container fulfill a condition.

pw::containers::ForEach()#

Container-based version of the <algorithm> std::for_each() function to apply a function to a container’s elements.

pw::containers::Find()#

Container-based version of the <algorithm> std::find() function to find the first element containing the passed value within a container value.

pw::containers::FindIf()#

Container-based version of the <algorithm> std::find_if() function to find the first element in a container matching the given condition.

pw::containers::FindIfNot()#

Container-based version of the <algorithm> std::find_if_not() function to find the first element in a container not matching the given condition.

pw::containers::FindEnd()#

Container-based version of the <algorithm> std::find_end() function to find the last subsequence within a container.

pw::containers::FindFirstOf()#

Container-based version of the <algorithm> std::find_first_of() function to find the first element within the container that is also within the options container.

pw::containers::AdjacentFind()#

Container-based version of the <algorithm> std::adjacent_find() function to find equal adjacent elements within a container.

pw::containers::Count()#

Container-based version of the <algorithm> std::count() function to count values that match within a container.

pw::containers::CountIf()#

Container-based version of the <algorithm> std::count_if() function to count values matching a condition within a container.

pw::containers::Mismatch()#

Container-based version of the <algorithm> std::mismatch() function to return the first element where two ordered containers differ. Applies == to the first N elements of c1 and c2, where N = min(size(c1), size(c2)). the function’s test condition. Applies pred to the first N elements of c1 and c2, where N = min(size(c1), size(c2)).

bool pw::containers::Equal()#

Container-based version of the <algorithm> std::equal() function to test whether two containers are equal.

Note

The semantics of Equal() are slightly different than those of std::equal(): while the latter iterates over the second container only up to the size of the first container, Equal() also checks whether the container sizes are equal. This better matches expectations about Equal() based on its signature.

bool pw::containers::IsPermutation()#

Container-based version of the <algorithm> std::is_permutation() function to test whether a container is a permutation of another.

pw::containers::Search()#

Container-based version of the <algorithm> std::search() function to search a container for a subsequence.

pw::containers::SearchN()#

Container-based version of the <algorithm> std::search_n() function to search a container for the first sequence of N elements.

Compatibility#

  • C++17

Dependencies#

  • pw_span

Zephyr#

To enable pw_containers for Zephyr add CONFIG_PIGWEED_CONTAINERS=y to the project’s configuration.