blob: a2cb0c5479e0995cd4c52e761baa3dc66acef8bc [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
=========
Implementation-side
-------------------
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, pw::Function<void(int result)> callback);
An API can accept a function either by value or by reference. If taken by value,
the implementation is responsible for managing the function by moving it into an
appropriate location.
.. admonition:: Value or reference?
It is preferable for APIs to take functions by value rather than by reference.
This provides callers of the API with a more convenient interface, as well as
making their lives easier by not requiring management of resources or
lifetimes.
Caller-side
-----------
When calling an API which takes a function by reference, the standard pattern is
to implicitly construct the function in place from a callable object. Simply
pass the desired callable directly to the API.
.. code-block:: c++
// Implicitly initialize a Function from a capturing lambda.
DoTheThing(42, [this](int result) { result_ = result; });
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.