blob: 14ce9060804e06cb3f1358be53efa248e362f323 [file] [log] [blame]
.. _module-pw_function:
-----------
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.
.. code-block:: c++
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.
.. code-block:: c++
// 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.
.. code-block:: c++
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.
.. admonition:: 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.
.. code-block:: c++
// 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()));
..
For larger callables, a ``Function`` can be constructed with an external buffer
in which the callable should be stored. The user must ensure that the lifetime
of the buffer exceeds that of the function object.
.. code-block:: c++
// Initialize a function with an external 16-byte buffer in which to store its
// callable. The callable will be stored in the buffer regardless of whether
// it fits inline.
pw::FunctionStorage<16> storage;
pw::Function<int()> get_random_number([]() { return 4; }, storage);
.. admonition:: External storage
Functions which use external storage still take up the configured inline
storage size, which should be accounted for when storing function objects.
In the future, ``pw::Function`` may support dynamic allocation of callable
storage using the system allocator. This operation will always be explicit.
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.
.. code-block:: c++
// 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.
.. code-block:: c++
// 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);
}
.. admonition:: 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.
.. code-block:: c++
// 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.
.. code-block:: c++
// 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.
.. include:: function_size
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.
.. include:: callable_size
Design
======
``pw::Function`` is based largely on
`fbl::Function <https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/fbl/include/fbl/function.h>`_
from Fuchsia with some changes to make it more suitable for embedded
development.
Functions are movable, but not copyable. This allows them to store and manage
callables without having to perform bookkeeping such as reference counting, and
avoids any reliance on dynamic memory management. The result is a simpler
implementation which is easy to conceptualize and use in an embedded context.
Zephyr
======
To enable ``pw_function` for Zephyr add ``CONFIG_PIGWEED_FUNCTION=y`` to the
project's configuration.