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::Functionis 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::Functionthat exclusively uses inline storage.IMPORTANT: If
pw::Functionis configured to allow dynamic allocations then any attempt to convertpw::InlineFunctiontopw::Functionwill 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::Callbackis identical topw::Functionexcept:On the first call to invoke a
pw::Callback, the target function held by thepw::Callbackcannot be called again.When a
pw::Callbackis 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::Callbackin the “already called” state has the same state as apw::Callbackthat 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::Callbackthat 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::Functioninstances and move operations.Pass by const reference (
const pw::Function&): When thepw::Functionis only invoked.When a
pw::Functionis called or inspected, but not moved, take a const reference to avoid copies and support temporaries.Pass by rvalue reference (
pw::Function&&): When thepw::Functionis moved.When the function takes ownership of the
pw::Functionobject, always use an rvalue reference (pw::Function<void()>&&) instead of a mutable lvalue reference (pw::Function<void()>&). An rvalue reference forces the caller tostd::movewhen passing a preexistingpw::Functionvariable, 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::Functionvariable. Use an rvalue reference instead if thepw::Functionis 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::Functionorpw::Callback, except that the last parameter is avoid*.GetFunctionPointerContextFirstplaces 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&)# GetFunctionPointeroverload 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&)# GetFunctionPointerContextFirstoverload that uses the type of the function passed to this call.
ScopeGuard#
-
template<typename Functor>
class ScopeGuard# ScopeGuardensures 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 theFunctorwhen 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.