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",
# ...
]
}
This assumes that your Bazel WORKSPACE
has a repository named @pigweed
that points to the upstream Pigweed repository.
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(). 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#
You can configure the inline allocation size of pw::Function
and whether it
dynamically allocates, but it applies to all uses of pw::Function
.
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#
One use case for invoking pw_function
from a C-style API is to automate the
generation of trampoline layers. See
pw::function::GetFunctionPointer()
.
API reference#
pw::Function
#
-
template<typename FunctionType, std::size_t inline_target_size = function_internal::config::kInlineCallableSize, typename Allocator = PW_FUNCTION_DEFAULT_ALLOCATOR_TYPE>
using pw::Function = fit::function_impl<inline_target_size, !function_internal::config::kEnableDynamicAllocation, FunctionType, Allocator># 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, const 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 Parameters:
Allocator – The Allocator used to dynamically allocate the callable, if it exceeds
inline_target_size
and dynamic allocation is enabled. Itsvalue_type
is irrelevant, since it must support rebinding.
pw::InlineFunction
#
-
template<typename FunctionType, std::size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::InlineFunction = fit::inline_function<FunctionType, 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 convertpw::InlineFunction
topw::Function
will ALWAYS allocate.
pw::Callback
#
-
template<typename FunctionType, std::size_t inline_target_size = function_internal::config::kInlineCallableSize, typename Allocator = PW_FUNCTION_DEFAULT_ALLOCATOR_TYPE>
using pw::Callback = fit::callback_impl<inline_target_size, !function_internal::config::kEnableDynamicAllocation, FunctionType, Allocator># pw::Callback
is identical topw::Function
except:On the first call to invoke a
pw::Callback
, the target function held by thepw::Callback
cannot be called again.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 apw::Callback
that has been assigned tonullptr
.
pw::InlineCallback
#
-
template<typename FunctionType, std::size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::InlineCallback = fit::inline_callback<FunctionType, inline_target_size># Version of
pw::Callback
that exclusively uses inline storage.
pw::bind_member()
#
-
template<auto method, typename T>
auto pw::bind_member(T *instance)# Returns a
Callable
which, when called, invokesmethod
oninstance
using the arguments provided.This is useful for binding the
this
argument of a callable.pw::bind_member<&T::MethodName>(instance)
is roughly equivalent to[instance](Arg arg1, ...) { instance->MethodName(arg1, ...) }
, albeit with proper support for overloads and argument forwarding.
pw::function::GetFunctionPointer()
#
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 avoid*
context argument. This makes it possible to use C++ callables with C-style APIs that take a function pointer andvoid*
context.The returned function pointer has the same return type and arguments as the
pw::Function
orpw::Callback
, except that the last parameter is avoid*
.GetFunctionPointerContextFirst
places thevoid*
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 avoid*
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.
pw::function::GetFunctionPointerContextFirst()
#
-
template<typename FunctionType>
constexpr auto pw::function::GetFunctionPointerContextFirst()# Same as
GetFunctionPointer
, but the context argument is passed first. Returns avoid(void*, int)
function for apw::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.
pw::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 theFunctor
when it goes out of scope.
-
inline void Dismiss()#
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 |
---|---|---|
Simple pw::Function vs. function pointer |
(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.
Label |
Segment |
Delta |
|||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Function pointer |
(ALL) |
0 |
|||||||||||||||
Static lambda (operator+) |
(ALL) |
0 |
|||||||||||||||
Non-capturing lambda |
(ALL) |
0 |
|||||||||||||||
Simple capturing lambda |
FLASH
|
+64 |
|||||||||||||||
Multi-argument capturing lambda |
FLASH
|
+64 |
|||||||||||||||
Custom class |
(ALL) |
0 |