pw_function#

The pw_function module provides a standard, general-purpose API for wrapping callable objects. pw_function is similar in spirit and API to std::function, but doesn’t allocate, and uses several tricks to prevent code bloat.

Overview#

Pigweed AI summary: The pw_function defines the pw::Function class, which is a move-only callable wrapper that can be constructed from any callable object. Functions are templated on the signature of the callable they store and implement the call operator. Functions are nullable, and invoking a null function triggers a runtime assert. The default constructor of pw::Function is constexpr, so default-constructed functions may be used in classes with constexpr constructors and in constinit expressions. By default, a Function stores its callable inline within the object,

Basic usage#

Pigweed AI summary: The "pw_function" defines the "pw::Function" class, which is a move-only callable wrapper that can be constructed from any callable object. Functions are templated on the signature of the callable they store and implement the call operator. Functions are nullable, and invoking a null function triggers a runtime assert. The "pw::Function" class's default constructor is constexpr, so default-constructed functions may be used in classes with constexpr constructors and in constinit expressions.

pw_function defines the pw::Function class. A Function is a move-only callable wrapper constructable from any callable object. Functions are templated on the signature of the callable they store.

Functions implement the call operator — invoking the object will forward to the stored callable.

int Add(int a, int b) { return a + b; }

// Construct a Function object from a function pointer.
pw::Function<int(int, int)> add_function(Add);

// Invoke the function object.
int result = add_function(3, 5);
EXPECT_EQ(result, 8);

// Construct a function from a lambda.
pw::Function<int(int)> negate([](int value) { return -value; });
EXPECT_EQ(negate(27), -27);

Functions are nullable. Invoking a null function triggers a runtime assert.

// A function initialized without a callable is implicitly null.
pw::Function<void()> null_function;

// Null functions may also be explicitly created or set.
pw::Function<void()> explicit_null_function(nullptr);

pw::Function<void()> function([]() {});  // Valid (non-null) function.
function = nullptr;  // Set to null, clearing the stored callable.

// Functions are comparable to nullptr.
if (function != nullptr) {
  function();
}

pw::Function’s default constructor is constexpr, so default-constructed functions may be used in classes with constexpr constructors and in constinit expressions.

class MyClass {
 public:
  // Default construction of a pw::Function is constexpr.
  constexpr MyClass() { ... }

  pw::Function<void(int)> my_function;
};

// pw::Function and classes that use it may be constant initialized.
constinit MyClass instance;

Storage#

Pigweed AI summary: This section discusses the storage of callable objects in the pw::Function class. By default, the callable is stored inline within the object, with a default size of two pointers. The pw::InlineFunction alias is always inlined, even if dynamic allocation is enabled for pw::Function. Attempting to construct a function from a callable larger than its inline size is a compile-time error unless dynamic allocation is enabled. The section also includes examples of constructing functions from lambdas and custom classes, and warns that

By default, a Function stores its callable inline within the object. The inline storage size defaults to the size of two pointers, but is configurable through the build system. The size of a Function object is equivalent to its inline storage size.

The pw::InlineFunction alias is similar to pw::Function, but is always inlined. That is, even if dynamic allocation is enabled for pw::Function, pw::InlineFunction will fail to compile if the callable is larger than the inline storage size.

Attempting to construct a function from a callable larger than its inline size is a compile-time error unless dynamic allocation is enabled.

Inline storage size

The default inline size of two pointers is sufficient to store most common callable objects, including function pointers, simple non-capturing and capturing lambdas, and lightweight custom classes.

// The lambda is moved into the function's internal storage.
pw::Function<int(int, int)> subtract([](int a, int b) { return a - b; });

// Functions can be also be constructed from custom classes that implement
// operator(). This particular object is large (8 ints of space).
class MyCallable {
 public:
  int operator()(int value);

 private:
  int data_[8];
};

// Compiler error: sizeof(MyCallable) exceeds function's inline storage size.
pw::Function<int(int)> function((MyCallable()));

Dynamic allocation

When PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION is enabled, a Function will use dynamic allocation to store callables that exceed the inline size. When it is enabled but a compile-time check for the inlining is still required pw::InlineFunction can be used.

Warning

If PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION is enabled then attempts to cast from :cpp:type:`pw::InlineFunction to a regular pw::Function will ALWAYS allocate memory.

API usage#

Reference#

template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::Function = fit::function_impl<inline_target_size, !function_internal::config::kEnableDynamicAllocation, Callable>#

pw::Function is a wrapper for an arbitrary callable object. It can be used by callback-based APIs to allow callers to provide any type of callable.

Example:

template <typename T>
bool All(const pw::Vector<T>& items,
         pw::Function<bool(const T& item)> predicate) {
  for (const T& item : items) {
    if (!predicate(item)) {
      return false;
    }
  }
  return true;
}

bool ElementsArePositive(const pw::Vector<int>& items) {
  return All(items, [](const int& i) { return i > 0; });
}

bool IsEven(const int& i) { return i % 2 == 0; }

bool ElementsAreEven(const pw::Vector<int>& items) {
  return All(items, IsEven);
}

template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::InlineFunction = fit::inline_function<Callable, inline_target_size>#

Version of pw::Function that exclusively uses inline storage.

IMPORTANT: If pw::Function is configured to allow dynamic allocations then any attempt to convert pw::InlineFunction to pw::Function will ALWAYS allocate.

template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::Callback = fit::callback_impl<inline_target_size, !function_internal::config::kEnableDynamicAllocation, Callable>#

pw::Callback is identical to pw::Function except:

  1. On the first call to invoke a pw::Callback, the target function held by the pw::Callback cannot be called again.

  2. When a pw::Callback is invoked for the first time, the target function is released and destructed, along with any resources owned by that function (typically the objects captured by a lambda).

A pw::Callback in the “already called” state has the same state as a pw::Callback that has been assigned to nullptr.

template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::InlineCallback = fit::inline_callback<Callable, inline_target_size>#

Version of pw::Callback that exclusively uses inline storage.

pw::Function as a function parameter#

Pigweed AI summary: This article discusses the use of pw::Function as a function parameter in C++. pw::Function can be used in place of a function pointer or equivalent callable when implementing an API that takes a callback. It is movable but not copyable, so APIs must accept pw::Function objects either by const reference or rvalue reference. The article provides rules of thumb for passing a pw::Function to a function, including passing by const reference when the pw::Function is only invoked and passing by rvalue reference

When implementing an API which takes a callback, a Function can be used in place of a function pointer or equivalent callable.

// Before:
void DoTheThing(int arg, void (*callback)(int result));

// After. Note that it is possible to have parameter names within the function
// signature template for clarity.
void DoTheThing(int arg, const pw::Function<void(int result)>& callback);

pw::Function is movable, but not copyable, so APIs must accept pw::Function objects either by const reference (const pw::Function<void()>&) or rvalue reference (const pw::Function<void()>&&). If the pw::Function simply needs to be called, it should be passed by const reference. If the pw::Function needs to be stored, it should be passed as an rvalue reference and moved into a pw::Function variable as appropriate.

// This function calls a pw::Function but doesn't store it, so it takes a
// const reference.
void CallTheCallback(const pw::Function<void(int)>& callback) {
  callback(123);
}

// This function move-assigns a pw::Function to another variable, so it takes
// an rvalue reference.
void StoreTheCallback(pw::Function<void(int)>&& callback) {
  stored_callback_ = std::move(callback);
}

Rules of thumb for passing a pw::Function to a function

  • Pass by value: Never.

    This results in unnecessary pw::Function instances and move operations.

  • Pass by const reference (const pw::Function&): When the pw::Function is only invoked.

    When a pw::Function is called or inspected, but not moved, take a const reference to avoid copies and support temporaries.

  • Pass by rvalue reference (pw::Function&&): When the pw::Function is moved.

    When the function takes ownership of the pw::Function object, always use an rvalue reference (pw::Function<void()>&&) instead of a mutable lvalue reference (pw::Function<void()>&). An rvalue reference forces the caller to std::move when passing a preexisting pw::Function variable, which makes the transfer of ownership explicit. It is possible to move-assign from an lvalue reference, but this fails to make it obvious to the caller that the object is no longer valid.

  • Pass by non-const reference (pw::Function&): Rarely, when modifying a variable.

    Non-const references are only necessary when modifying an existing pw::Function variable. Use an rvalue reference instead if the pw::Function is moved into another variable.

Calling functions that use pw::Function#

Pigweed AI summary: This section explains how to call functions that use pw::Function, which can be constructed from any callback object. When calling an API that takes pw::Function, simply pass the callable object without creating an intermediate pw::Function object. When working with an existing pw::Function variable, it can be passed directly to functions that take a const reference. If the function takes ownership of the pw::Function, move the pw::Function variable at the call site. Examples of code are provided to illustrate these concepts

A pw::Function can be implicitly constructed from any callback object. When calling an API that takes a pw::Function, simply pass the callable object. There is no need to create an intermediate pw::Function object.

// Implicitly creates a pw::Function from a capturing lambda and calls it.
CallTheCallback([this](int result) { result_ = result; });

// Implicitly creates a pw::Function from a capturing lambda and stores it.
StoreTheCallback([this](int result) { result_ = result; });

When working with an existing pw::Function variable, the variable can be passed directly to functions that take a const reference. If the function takes ownership of the pw::Function, move the pw::Function variable at the call site.

// Accepts the pw::Function by const reference.
CallTheCallback(my_function_);

// Takes ownership of the pw::Function.
void StoreTheCallback(std::move(my_function));

pw::Callback for one-shot functions#

Pigweed AI summary: The pw::Callback for one-shot functions is a specialized version of pw::Function that can only be called once. Once the pw::Callback is called, the target function is destroyed. A pw::Callback in the "already called" state is the same as a pw::Callback that has been assigned to nullptr.

pw::Callback is a specialization of pw::Function that can only be called once. After a pw::Callback is called, the target function is destroyed. A pw::Callback in the “already called” state has the same state as a pw::Callback that has been assigned to nullptr.

Invoking pw::Function from a C-style API#

Traditional callback APIs often use a function pointer and void* context argument. The context argument makes it possible to use the callback function with non-global data. For example, the qsort_s and bsearch_s functions take a pointer to a comparison function that has void* context as its last parameter. pw::Function does not naturally work with these kinds of APIs.

The functions below make it simple to adapt a pw::Function for use with APIs that accept a function pointer and void* context argument.

template<typename FunctionType>
constexpr auto pw::function::GetFunctionPointer()#

Returns a function pointer that invokes a pw::Function, lambda, or other callable object from a void* context argument. This makes it possible to use C++ callables with C-style APIs that take a function pointer and void* context.

The returned function pointer has the same return type and arguments as the pw::Function or pw::Callback, except that the last parameter is a void*. GetFunctionPointerContextFirst places the void* context parameter first.

The following example adapts a C++ lambda function for use with C-style API that takes an int (*)(int, void*) function and a void* context.

void TakesAFunctionPointer(int (*function)(int, void*), void* context);

void UseFunctionPointerApiWithPwFunction() {
  // Declare a callable object so a void* pointer can be obtained for it.
  auto my_function = [captures](int value) {
     // ...
     return value + captures;
  };

  // Invoke the API with the function pointer and callable pointer.
  TakesAFunctionPointer(pw::function::GetFunctionPointer(my_function),
                        &my_function);
}

The function returned from this must ONLY be used with the exact type for which it was created! Function pointer / context APIs are not type safe.

template<typename FunctionType>
constexpr auto pw::function::GetFunctionPointer(const FunctionType&)#

GetFunctionPointer overload that uses the type of the function passed to this call.

template<typename FunctionType>
constexpr auto pw::function::GetFunctionPointerContextFirst()#

Same as GetFunctionPointer, but the context argument is passed first. Returns a void(void*, int) function for a pw::Function<void(int)>.

template<typename FunctionType>
constexpr auto pw::function::GetFunctionPointerContextFirst(const FunctionType&)#

GetFunctionPointerContextFirst overload that uses the type of the function passed to this call.

ScopeGuard#

template<typename Functor>
class ScopeGuard#

ScopeGuard ensures that the specified functor is executed no matter how the current scope exits, unless it is dismissed.

Example:

pw::Status SomeFunction() {
  PW_TRY(OperationOne());
  ScopeGuard undo_operation_one(UndoOperationOne);
  PW_TRY(OperationTwo());
  ScopeGuard undo_operation_two(UndoOperationTwo);
  PW_TRY(OperationThree());
  undo_operation_one.Dismiss();
  undo_operation_two.Dismiss();
  return pw::OkStatus();
}

Public Functions

inline void Dismiss()#

Dismisses the ScopeGuard, meaning it will no longer execute the Functor when it goes out of scope.

Size reports#

Pigweed AI summary: This text describes size reports and callable sizes for different types of functions and callable objects. The size report compares an API using pw::Function to a traditional function pointer. The table below demonstrates typical sizes of various callable types, which can be used as a reference when sizing external buffers for Function objects. The callable types include function pointers, static lambda, non-capturing lambda, simple capturing lambda, multi-argument capturing lambda, and custom class.

Function class#

Pigweed AI summary: This section compares the size of an API using a pw::Function to a traditional function pointer, and presents a size report showing no difference in size between the two.

The following size report compares an API using a pw::Function to a traditional function pointer.

Label

Segment

Delta

Simple pw::Function vs. function pointer

(ALL)

0

Callable sizes#

Pigweed AI summary: This section provides a table of typical sizes for various callable types, which can be used as a reference when sizing external buffers for Function objects. The table includes sizes for function pointers, static lambdas, non-capturing lambdas, simple capturing lambdas, multi-argument capturing lambdas, and custom classes.

The table below demonstrates typical sizes of various callable types, which can be used as a reference when sizing external buffers for Function objects.

Label

Segment

Delta

Function pointer

(ALL)

0

Static lambda (operator+)

(ALL)

0

Non-capturing lambda

(ALL)

0

Simple capturing lambda

FLASH

-2

pw::allocator::FreeList::RemoveChunk()

+2

pw_boot_PreStaticMemoryInit

+34

main

NEW

+24

__cxa_guard_acquire

NEW

+6

__cxa_guard_release

+64

Multi-argument capturing lambda

FLASH

-2

pw::allocator::FreeList::RemoveChunk()

+2

pw_boot_PreStaticMemoryInit

+34

main

NEW

+24

__cxa_guard_acquire

NEW

+6

__cxa_guard_release

+64

Custom class

(ALL)

0

Design#

Pigweed AI summary: This section explains that pw::Function and pw::Callback are aliases of fit::function and fit::callback respectively, with links to their definitions.

pw::Function is an alias of fit::function.

pw::Callback is an alias of fit::callback.

Zephyr#

Pigweed AI summary: The Zephyr platform can enable the pw_function by adding "CONFIG_PIGWEED_FUNCTION=y" to the project's configuration.

To enable pw_function` for Zephyr add ``CONFIG_PIGWEED_FUNCTION=y to the project’s configuration.