Alexei Frolov | 99de52d | 2021-05-11 19:58:01 -0700 | [diff] [blame] | 1 | .. _module-pw_function: |
| 2 | |
| 3 | ----------- |
| 4 | pw_function |
| 5 | ----------- |
| 6 | The function module provides a standard, general-purpose API for wrapping |
| 7 | callable objects. |
| 8 | |
| 9 | .. note:: |
| 10 | This module is under construction and its API is not complete. |
| 11 | |
| 12 | Overview |
| 13 | ======== |
| 14 | |
| 15 | Basic usage |
| 16 | ----------- |
| 17 | ``pw_function`` defines the ``pw::Function`` class. A ``Function`` is a |
| 18 | move-only callable wrapper constructable from any callable object. Functions |
| 19 | are templated on the signature of the callable they store. |
| 20 | |
| 21 | Functions implement the call operator --- invoking the object will forward to |
| 22 | the stored callable. |
| 23 | |
| 24 | .. code-block:: c++ |
| 25 | |
| 26 | int Add(int a, int b) { return a + b; } |
| 27 | |
| 28 | // Construct a Function object from a function pointer. |
| 29 | pw::Function<int(int, int)> add_function(Add); |
| 30 | |
| 31 | // Invoke the function object. |
| 32 | int result = add_function(3, 5); |
| 33 | EXPECT_EQ(result, 8); |
| 34 | |
| 35 | // Construct a function from a lambda. |
| 36 | pw::Function<int(int)> negate([](int value) { return -value; }); |
| 37 | EXPECT_EQ(negate(27), -27); |
| 38 | |
| 39 | Functions are nullable. Invoking a null function triggers a runtime assert. |
| 40 | |
| 41 | .. code-block:: c++ |
| 42 | |
| 43 | // A function intialized without a callable is implicitly null. |
| 44 | pw::Function<void()> null_function; |
| 45 | |
| 46 | // Null functions may also be explicitly created or set. |
| 47 | pw::Function<void()> explicit_null_function(nullptr); |
| 48 | |
| 49 | pw::Function<void()> function([]() {}); // Valid (non-null) function. |
| 50 | function = nullptr; // Set to null, clearing the stored callable. |
| 51 | |
| 52 | // Functions are comparable to nullptr. |
| 53 | if (function != nullptr) { |
| 54 | function(); |
| 55 | } |
| 56 | |
Wyatt Hepler | f5cdd93 | 2021-06-14 13:53:23 -0700 | [diff] [blame] | 57 | ``pw::Function``'s default constructor is ``constexpr``, so default-constructed |
| 58 | functions may be used in classes with ``constexpr`` constructors and in |
| 59 | ``constinit`` expressions. |
| 60 | |
| 61 | .. code-block:: c++ |
| 62 | |
| 63 | class MyClass { |
| 64 | public: |
| 65 | // Default construction of a pw::Function is constexpr. |
| 66 | constexpr MyClass() { ... } |
| 67 | |
| 68 | pw::Function<void(int)> my_function; |
| 69 | }; |
| 70 | |
| 71 | // pw::Function and classes that use it may be constant initialized. |
| 72 | constinit MyClass instance; |
| 73 | |
Alexei Frolov | 99de52d | 2021-05-11 19:58:01 -0700 | [diff] [blame] | 74 | Storage |
| 75 | ------- |
| 76 | By default, a ``Function`` stores its callable inline within the object. The |
| 77 | inline storage size defaults to the size of two pointers, but is configurable |
| 78 | through the build system. The size of a ``Function`` object is equivalent to its |
| 79 | inline storage size. |
| 80 | |
| 81 | Attempting to construct a function from a callable larger than its inline size |
| 82 | is a compile-time error. |
| 83 | |
| 84 | .. admonition:: Inline storage size |
| 85 | |
| 86 | The default inline size of two pointers is sufficient to store most common |
| 87 | callable objects, including function pointers, simple non-capturing and |
| 88 | capturing lambdas, and lightweight custom classes. |
| 89 | |
| 90 | .. code-block:: c++ |
| 91 | |
| 92 | // The lambda is moved into the function's internal storage. |
| 93 | pw::Function<int(int, int)> subtract([](int a, int b) { return a - b; }); |
| 94 | |
| 95 | // Functions can be also be constructed from custom classes that implement |
| 96 | // operator(). This particular object is large (8 ints of space). |
| 97 | class MyCallable { |
| 98 | public: |
| 99 | int operator()(int value); |
| 100 | |
| 101 | private: |
| 102 | int data_[8]; |
| 103 | }; |
| 104 | |
| 105 | // Compiler error: sizeof(MyCallable) exceeds function's inline storage size. |
| 106 | pw::Function<int(int)> function((MyCallable())); |
| 107 | |
| 108 | .. |
| 109 | For larger callables, a ``Function`` can be constructed with an external buffer |
| 110 | in which the callable should be stored. The user must ensure that the lifetime |
| 111 | of the buffer exceeds that of the function object. |
| 112 | |
| 113 | .. code-block:: c++ |
| 114 | |
| 115 | // Initialize a function with an external 16-byte buffer in which to store its |
| 116 | // callable. The callable will be stored in the buffer regardless of whether |
| 117 | // it fits inline. |
| 118 | pw::FunctionStorage<16> storage; |
| 119 | pw::Function<int()> get_random_number([]() { return 4; }, storage); |
| 120 | |
| 121 | .. admonition:: External storage |
| 122 | |
| 123 | Functions which use external storage still take up the configured inline |
| 124 | storage size, which should be accounted for when storing function objects. |
| 125 | |
| 126 | In the future, ``pw::Function`` may support dynamic allocation of callable |
| 127 | storage using the system allocator. This operation will always be explicit. |
| 128 | |
| 129 | API usage |
| 130 | ========= |
| 131 | |
| 132 | Implementation-side |
| 133 | ------------------- |
| 134 | When implementing an API which takes a callback, a ``Function`` can be used in |
| 135 | place of a function pointer or equivalent callable. |
| 136 | |
| 137 | .. code-block:: c++ |
| 138 | |
| 139 | // Before: |
| 140 | void DoTheThing(int arg, void (*callback)(int result)); |
| 141 | |
| 142 | // After. Note that it is possible to have parameter names within the function |
| 143 | // signature template for clarity. |
| 144 | void DoTheThing(int arg, pw::Function<void(int result)> callback); |
| 145 | |
| 146 | An API can accept a function either by value or by reference. If taken by value, |
| 147 | the implementation is responsible for managing the function by moving it into an |
| 148 | appropriate location. |
| 149 | |
| 150 | .. admonition:: Value or reference? |
| 151 | |
| 152 | It is preferable for APIs to take functions by value rather than by reference. |
| 153 | This provides callers of the API with a more convenient interface, as well as |
| 154 | making their lives easier by not requiring management of resources or |
| 155 | lifetimes. |
| 156 | |
| 157 | Caller-side |
| 158 | ----------- |
| 159 | When calling an API which takes a function by reference, the standard pattern is |
| 160 | to implicitly construct the function in place from a callable object. Simply |
| 161 | pass the desired callable directly to the API. |
| 162 | |
| 163 | .. code-block:: c++ |
| 164 | |
| 165 | // Implicitly initialize a Function from a capturing lambda. |
| 166 | DoTheThing(42, [this](int result) { result_ = result; }); |
| 167 | |
| 168 | Size reports |
| 169 | ============ |
| 170 | |
| 171 | Function class |
| 172 | -------------- |
| 173 | The following size report compares an API using a ``pw::Function`` to a |
| 174 | traditional function pointer. |
| 175 | |
| 176 | .. include:: function_size |
| 177 | |
| 178 | Callable sizes |
| 179 | -------------- |
| 180 | The table below demonstrates typical sizes of various callable types, which can |
| 181 | be used as a reference when sizing external buffers for ``Function`` objects. |
| 182 | |
| 183 | .. include:: callable_size |
| 184 | |
| 185 | Design |
| 186 | ====== |
| 187 | ``pw::Function`` is based largely on |
| 188 | `fbl::Function <https://cs.opensource.google/fuchsia/fuchsia/+/main:zircon/system/ulib/fbl/include/fbl/function.h>`_ |
| 189 | from Fuchsia with some changes to make it more suitable for embedded |
| 190 | development. |
| 191 | |
Alexei Frolov | bebba90 | 2021-06-09 17:03:52 -0700 | [diff] [blame] | 192 | Functions are movable, but not copyable. This allows them to store and manage |
Alexei Frolov | 99de52d | 2021-05-11 19:58:01 -0700 | [diff] [blame] | 193 | callables without having to perform bookkeeping such as reference counting, and |
| 194 | avoids any reliance on dynamic memory management. The result is a simpler |
| 195 | implementation which is easy to conceptualize and use in an embedded context. |