pw_function#
The pw_function
module provides a standard, general-purpose API for
wrapping callable objects. pw_function
is similar in spirit and API to
std::function
, but doesn’t allocate, and uses several tricks to prevent
code bloat.
Overview#
Pigweed AI summary: The pw_function defines the pw::Function class, which is a move-only callable wrapper that can be constructed from any callable object. Functions are templated on the signature of the callable they store and implement the call operator. Functions are nullable, and invoking a null function triggers a runtime assert. The default constructor of pw::Function is constexpr, so default-constructed functions may be used in classes with constexpr constructors and in constinit expressions. By default, a Function stores its callable inline within the object,
Basic usage#
Pigweed AI summary: The "pw_function" defines the "pw::Function" class, which is a move-only callable wrapper that can be constructed from any callable object. Functions are templated on the signature of the callable they store and implement the call operator. Functions are nullable, and invoking a null function triggers a runtime assert. The "pw::Function" class's default constructor is constexpr, so default-constructed functions may be used in classes with constexpr constructors and in constinit expressions.
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 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();
}
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#
Pigweed AI summary: This section discusses the storage of callable objects in the pw::Function class. By default, the callable is stored inline within the object, with a default size of two pointers. The pw::InlineFunction alias is always inlined, even if dynamic allocation is enabled for pw::Function. Attempting to construct a function from a callable larger than its inline size is a compile-time error unless dynamic allocation is enabled. The section also includes examples of constructing functions from lambdas and custom classes, and warns that
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.
The pw::InlineFunction
alias 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 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.
When it 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 :cpp:type:`pw::InlineFunction to a regular pw::Function
will ALWAYS allocate memory.
API usage#
Reference#
-
template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::Function = fit::function_impl<inline_target_size, !function_internal::config::kEnableDynamicAllocation, Callable># 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, 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<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::InlineFunction = fit::inline_function<Callable, 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.
-
template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::Callback = fit::callback_impl<inline_target_size, !function_internal::config::kEnableDynamicAllocation, Callable># 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
.
-
template<typename Callable, size_t inline_target_size = function_internal::config::kInlineCallableSize>
using pw::InlineCallback = fit::inline_callback<Callable, inline_target_size># Version of
pw::Callback
that exclusively uses inline storage.
pw::Function
as a function parameter#
Pigweed AI summary: This article discusses the use of pw::Function as a function parameter in C++. pw::Function can be used in place of a function pointer or equivalent callable when implementing an API that takes a callback. It is movable but not copyable, so APIs must accept pw::Function objects either by const reference or rvalue reference. The article provides rules of thumb for passing a pw::Function to a function, including passing by const reference when the pw::Function is only invoked and passing by rvalue reference
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 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
#
Pigweed AI summary: This section explains how to call functions that use pw::Function, which can be constructed from any callback object. When calling an API that takes pw::Function, simply pass the callable object without creating an intermediate pw::Function object. When working with an existing pw::Function variable, it 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. Examples of code are provided to illustrate these concepts
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));
pw::Callback
for one-shot functions#
Pigweed AI summary: The pw::Callback for one-shot functions is a specialized version of pw::Function that can only be called once. Once the pw::Callback is called, the target function is destroyed. A pw::Callback in the "already called" state is the same as a pw::Callback that has been assigned to nullptr.
pw::Callback
is a specialization of pw::Function
that
can only be called once. After a pw::Callback
is called, the target
function is destroyed. A pw::Callback
in the “already called” state
has the same state as a pw::Callback
that has been assigned to
nullptr.
Invoking pw::Function
from a C-style API#
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.
-
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.
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()#
Size reports#
Pigweed AI summary: This text describes size reports and callable sizes for different types of functions and callable objects. The size report compares an API using pw::Function to a traditional function pointer. The table below demonstrates typical sizes of various callable types, which can be used as a reference when sizing external buffers for Function objects. The callable types include function pointers, static lambda, non-capturing lambda, simple capturing lambda, multi-argument capturing lambda, and custom class.
Function class#
Pigweed AI summary: This section compares the size of an API using a pw::Function to a traditional function pointer, and presents a size report showing no difference in size between the two.
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 |
Callable sizes#
Pigweed AI summary: This section provides a table of typical sizes for various callable types, which can be used as a reference when sizing external buffers for Function objects. The table includes sizes for function pointers, static lambdas, non-capturing lambdas, simple capturing lambdas, multi-argument capturing lambdas, and custom classes.
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 |
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 |
Design#
Pigweed AI summary: This section explains that pw::Function and pw::Callback are aliases of fit::function and fit::callback respectively, with links to their definitions.
pw::Function
is an alias of
fit::function.
pw::Callback
is an alias of
fit::callback.
Zephyr#
Pigweed AI summary: The Zephyr platform can enable the pw_function by adding "CONFIG_PIGWEED_FUNCTION=y" to the project's configuration.
To enable pw_function` for Zephyr add ``CONFIG_PIGWEED_FUNCTION=y
to the
project’s configuration.