pw_function#

The function module provides a standard, general-purpose API for wrapping callable objects.

Note

This module is under construction and its API is not complete.

Overview#

Basic usage#

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 intialized 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#

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.

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.

API usage#

pw::Function function parameters#

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#

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));

Size reports#

Function class#

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

Label

Segment

Before

Delta

After

Simple pw::Function vs. function pointer

(all)
(same)
0
(same)

Callable sizes#

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

Before

Delta

After

Function pointer

(all)
(same)
0
(same)

Static lambda (operator+)

(all)
(same)
0
(same)

Non-capturing lambda

(all)
(same)
0
(same)

Simple capturing lambda

FLASH
RAM
20,072
656
+64
+8
20,136
664

Multi-argument capturing lambda

FLASH
RAM
20,072
656
+64
+8
20,136
664

Custom class

(all)
(same)
0
(same)

Design#

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

Zephyr#

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