pw_function#
Embedded-friendly std::function
Stable C++17
Familiar.
pw_function
provides a standard, general-purpose API for wrapping callable objects that’s similar to std::function.Optimized.
pw_function
doesn’t allocate (unless you want it to) and uses several tricks to prevent code bloat.
#include "pw_function/function.h"
// pw::Function can be constructed from a function pointer...
int _a(int a, int b) { return a + b; }
pw::Function<int(int, int)> add(_a);
// ... or a lambda.
pw::Function<int(int)> square([](int num) { return num * num; });
// pw::Callback can only be invoked once. After the first call, the target
// function is released and destroyed, along with any resources owned by
// that function.
pw::Callback<void(void)> flip_table_once([](void) {
// (╯°□°)╯︵ ┻━┻
});
add(5, 6);
add = nullptr; // pw::Function and pw::Callback are nullable
add(7, 2); // CRASH
square(4);
if (flip_table_once != nullptr) { // Safe to call
flip_table_once();
} else {
// ┬─┬ノ( º _ ºノ)
}
Get started#
Add @pigweed//pw_function
to your target’s deps
:
cc_library("...") {
# ...
deps = [
# ...
"@pigweed//pw_function",
# ...
]
}
Add $dir_pw_function
to your target’s deps
:
pw_executable("...") {
# ...
deps = [
# ...
"$dir_pw_function",
# ...
]
}
Link your library to pw_function
:
add_library(my_lib ...)
target_link_libraries(my_lib PUBLIC pw_function)
Use pw_function
in your C++ code:
#include "pw_function/function.h"
// ...
Guides#
Construct pw::Function
from a function pointer#
pw::Function
is a move-only callable wrapper constructable from any
callable object. It’s templated on the signature of the callable it stores and
implements the call operator; invoking a pw::Function
object forwards 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 pw::Function
from a lambda#
// Construct a function from a lambda.
pw::Function<int(int)> negate([](int value) { return -value; });
EXPECT_EQ(negate(27), -27);
Create single-use functions with pw::Callback
#
pw::Callback
is a specialization of pw::Function
that
can only be called once. After a pw::Callback
is called, the target
function is released and destroyed, along with any resources owned by that
function. A pw::Callback
in the “already called” state
has the same state as a pw::Function
that has been assigned to
nullptr.
pw::Callback<void(void)> flip_table_once([](void) {
// (╯°□°)╯︵ ┻━┻
});
flip_table_once(); // OK
flip_table_once(); // CRASH
Nullifying functions and comparing to null#
pw::Function
and pw::Callback
are nullable and can be compared to
nullptr
. 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();
}
constexpr
constructors and constinit
expressions#
The default constructor for pw::Function
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;
pw::Function
as a function parameter#
When implementing an API which uses callbacks, pw::Function
can be used in
place of a function pointer or equivalent callable.
// Before:
void DoTheThing(int arg, void (*callback)(int result));
// After:
void DoTheThing(int arg, const pw::Function<void(int result)>& callback);
// Note the parameter name within the function signature template for clarity.
Move semantics#
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 thepw::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 thepw::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 tostd::move
when passing a preexistingpw::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 thepw::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));
Managing inline storage size#
By default, pw::Function
stores its callable inline within the object. The
inline storage size defaults to the size of one pointer, but is configurable
through the build system.
pw::InlineFunction
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 one pointer 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().
class Add {
public:
Add(int value) : value_(value) {}
int operator()(int number) { return value_ + number; }
private:
int value_;
};
// The object is moved into the function's internal storage.
pw::Function<int(int)> add_100(Add(100));
// This particular object is too large (8 ints of space) for pw::Function as
// configured.
class MyCallable {
public:
int operator()(int index) const { return data_[index]; }
private:
int data_[8] = {1, 2, 3, 4, 5, 6, 7, 8};
};
#ifdef CODE_DOES_NOT_COMPILE
// Compiler error: sizeof(MyCallable) exceeds function's inline storage size.
pw::Function<int(int)> big_function((MyCallable()));
#endif
// This large callable could be stored in a custom-sized InlineFunction.
pw::InlineFunction<int(int), sizeof(MyCallable)> big_function((MyCallable()));
Dynamic allocation#
You can configure the inline allocation size of pw::Function
and
whether it dynamically allocates, but it applies to all uses of
pw::Function
. If dynamic allocation is required, use
pw::DynamicFunction
. Note that using multiple variations of
pw::Function
increases code size, and conversions between them may
not be efficient or possible in all cases.
As mentioned in Design, pw::Function
is an alias
of Fuchsia’s fit::function
. fit::function
allows you to specify the
inline (static) allocation size and whether to dynamically allocate if the
callable target doesn’t inline. If you want to use a function class with
different attributes, you can interact with fit::function
directly but note
that the resulting functions may not be interchangeable, i.e. callables for one
might not fit in the other.
When PW_FUNCTION_ENABLE_DYNAMIC_ALLOCATION
is enabled, a pw::Function
will use dynamic allocation to store callables that exceed the inline size.
An allocator type can be optionally supplied as a
template argument. The default allocator type can also be changed by overriding
PW_FUNCTION_DEFAULT_ALLOCATOR_TYPE
(the value_type
of the allocator
is irrelevant, since it must support rebinding). When dynamic allocation 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 pw::InlineFunction
to a regular
pw::Function
will ALWAYS allocate memory.
Note
When building Pigweed itself for host platforms, we enable dynamic
allocation. This is required for some modules that use pw::Function
,
like pw_bluetooth_sapphire. But it is not the default for
downstream projects because it introduces a difference between host and
non-host builds. This difference has the potential to cause breakages if
code is built for host first, and then later ported to device.
Invoking pw::Function
from a C-style API#
When invoking a pw::Function
from a C-style API, a trampoline
layer may be necessary.
Use pw::function::GetFunctionPointer()
to generate a trampoline
layer for a pw::Function
automatically.
API reference#
Moved: pw_function
Design#
pw::Function
is an alias of Fuchsia’s fit::function_impl
and
pw::Callback
is an alias of Fuchsia’s fit::callback_impl
. See the
following links for more information about Fuchsia’s implementations:
Why pw::Function
is not a literal#
The default constructor for pw::Function
is constexpr
but
pw::Function
is not a literal type. Instances can be declared constinit
but can’t be used in constexpr
contexts. There are a few reasons for this:
pw::Function
supports wrapping any callable type, and the wrapped type might not be a literal type.pw::Function
stores inline callables in a bytes array, which is notconstexpr
-friendly.pw::Function
optionally uses dynamic allocation, which doesn’t work inconstexpr
contexts (at least before C++20).
Size reports#
Comparing pw::Function
to a traditional function pointer#
The following size report compares an API using a pw::Function
to a
traditional function pointer.
Label |
Segment |
Delta |
||||
---|---|---|---|---|---|---|
Function pointer |
(ALL) |
0 |
||||
Static lambda (operator+) |
(ALL) |
0 |
||||
Non-capturing lambda |
(ALL) |
0 |
||||
Simple capturing lambda |
FLASH
|
+16 |
||||
Multi-argument capturing lambda |
FLASH
|
+16 |
||||
Custom class |
(ALL) |
0 |
Typical sizes of various callable types#
The table below demonstrates typical sizes of various callable types, which can
be used as a reference when sizing external buffers for pw::Function
objects.
Note
The size report that is usually displayed here is temporarily unavailable while we migrate the pigweed.dev build system from GN to Bazel. See b/388905812 for updates.