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 the pw::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 the pw::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 to std::move when passing a preexisting pw::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 the pw::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. Its value_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 convert pw::InlineFunction to pw::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 to pw::Function except:

  1. On the first call to invoke a pw::Callback, the target function held by the pw::Callback cannot be called again.

  2. 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 a pw::Callback that has been assigned to nullptr.

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, invokes method on instance 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 a void* context argument. This makes it possible to use C++ callables with C-style APIs that take a function pointer and void* context.

The returned function pointer has the same return type and arguments as the pw::Function or pw::Callback, except that the last parameter is a void*. GetFunctionPointerContextFirst places the void* 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 a void* 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 a void(void*, int) function for a pw::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 the Functor when it goes out of scope.

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 not constexpr-friendly.

  • pw::Function optionally uses dynamic allocation, which doesn’t work in constexpr 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

+2

[section .code]

-4

quorem

+36

main

NEW

+24

__cxa_guard_acquire

NEW

+6

__cxa_guard_release

+64

Multi-argument capturing lambda

FLASH

+2

[section .code]

-4

quorem

+36

main

NEW

+24

__cxa_guard_acquire

NEW

+6

__cxa_guard_release

+64

Custom class

(ALL)

0