Earlier I started elaborating on Direct-BT C++ implementation details, covering communication channels and data structures suitable for concurrency.
This part shall cover Jaulib’s jau::function<R(A…)>, used to manage callback lists e.g. in BTManager or simply utilizing them to delegate functionality like in jau::service_runner.
Updated on 2023-01-03: Matching new class name and properties.
Function Overview
Similar to std::function, jau::function<R(A…)> stores any callable target function solely described by its return type R
and arguments types A...
from any source, e.g. free functions, capturing and non-capturing lambda function, member functions.
jau::function<R(A…)> supports equality operations for all func::target_type source types, allowing to manage container of jau::functions, see limitations below.
If a jau::function contains no target, see jau::function<R(A…)>::is_null(), it is empty. Invoking the target of an empty jau::function is a no-operation and has no side effects.
jau::function satisfies the requirements of CopyConstructible, CopyAssignable, MoveConstructible and MoveAssignable.
Compared to std::function<R(A...)>
, jau::function<R(A...)>
- exposes the target function signature jau::type_info via jau::function::signature()
- supports equality operations
- supports Y combinator and deducing this lambda functions
- self contained low memory, cache friendly, static polymorphic target function delegate_t<R, A…> storage, see implementation details
- most operations are
noexcept
, except for the user given function invocation
Instances of jau::function can store, copy, move and invoke any of its callable targets
- free functions
- factory bind_free()
- includes non-capturing lambda
- constructor jau::function<R(A…)>::function(R(*func)(A…))
- factory bind_free()
- member functions
- factory bind_member()
- constructor `function(C *base, R(C::*mfunc)(A…))`
- lambda functions
- capturing and non-capturing lambdas as jau::function using func::lambda_target_t
- constructor `template<typename L> function(L func)`
- see limitations on their equality operator w/o RTTI on `gcc`.
- Y combinator and deducing this lambda functions
- factory bind_ylambda()
- lambda alike functions using a captured data type by-reference
- factory bind_capref()
- constructor function(I* data_ptr, R(*func)(I*, A…))
- lambda alike functions using a captured data type by value
- factory bind_capval()
- constructor copy function(const I& data, R(*func)(I&, A…))
- constructor move function(I&& data, R(*func)(I&, A…))
- std::function
- factory bind_std()
- constructor function(uint64_t id, std::function<R(A…)> func)
Implementation Details
jau::function<R(A...)>
holds the static polymorphic target function delegate_t<R, A…>,
which itself completely holds up to 24 bytes sized TriviallyCopyable
target function objects to avoiding cache misses.
The following table shows the full memory footprint of the target function delegate_t<R, A…> storage,
which equals to jau::function<R(A...)>
memory size as it only contains the instance of delegate_t<R, A…>.
Type | Signature | Target Function Size | delegate_t<R, A...> Size |
Heap Size | Total Size | TriviallyCopyable |
---|---|---|---|---|---|---|
free | function<free, void ()> | 8 | 48 | 0 | 48 | true |
member | function<member, int (int)> | 16 | 48 | 0 | 48 | true |
lambda_plain | function<lambda, int (int)> | 24 | 48 | 0 | 48 | true |
lambda_ref | function<lambda, int (int)> | 24 | 48 | 0 | 48 | true |
lambda_copy | function<lambda, int (int)> | 24 | 48 | 0 | 48 | true |
ylambda_plain | function<ylambda, int (int)> | 24 | 48 | 0 | 48 | true |
capval (small) | function<capval, int (int)> | 16 | 48 | 0 | 48 | true |
capval (big) | function<capval, int (int)> | 48 | 48 | 48 | 96 | true |
capref | function<capref, int (int)> | 16 | 48 | 0 | 48 | true |
Memory sizes are in bytes, data collected on a GNU/Linux arm64 system.
The detailed memory footprint can queried at runtime, see implementation of jau::function<R(A…)>::function::toString().
Static polymorphism is achieved by constructing the delegate_t<R, A…> instance via their func::target_type specific factories, see mapping at func::target_type.
For example the static func::member_target_t::delegate constructs its specific delegate_t<R, A…> by passing its data and required functions to func::delegate_t<R, A…>::make.
The latter is an overloaded template for trivial and non-trivial types and decides which memory is being used, e.g. the internal 24 bytes memory cache or heap if not fitting.
To support lambda identity for the equality operator, jau::type_info is being used either with Runtime Type Information (RTTI) if enabled or using Compile time type information (CTTI), see limitations below.
Function Usage
A detailed API usage is covered within test_functional.hpp and test_functional_perf.hpp, see function test00_usage()
.
Let’s assume we like to bind to the following function prototype bool func(int)
, which results to jau::function<bool(int)>
:
- Free functions via constructor and jau::bind_free()
- Prologue
typedef bool(*cfunc)(int); // to force non-capturing lambda into a free function template type deductionbool my_func(int v) { return 0 == v; }struct MyClass {static bool func(int v) { return 0 == v; }};
- Constructor function(R(*func)(A…))
jau::function<bool(int)> func0 = my_func;jau::function<bool(int)> func1 = &MyClass:func;return 0 == v;} );
- Factory
jau::bind_free(R(*func)(A...))
- Prologue
- Class member functions via constructor and jau::bind_member()
- Prologue
struct MyClass {bool m_func(int v) { return 0 == v; }};MyClass i1;
- Constructor function(C *base, R(C::*mfunc)(A…))
jau::function<bool(int)> func(&i1, &MyClass::m_func);
- Factory
jau::bind_member(C *base, R(C::*mfunc)(A...))
- Prologue
- Lambda functions via constructor
- Prologue
int sum = 0;
- Stateless lambda, equivalent to
bool(*)(int)
function - Stateless by-value capturing lambda
- Stateful by-value capturing lambda mutating captured field
- Stateless by-reference capturing lambda
- Stateless by-reference capturing lambda assigning an auto lambda
auto lambda_func = [&](int v) -> bool {sum += v;return 0 == v;};jau::function<bool(int)> func1 = lambda_func;jau::function<bool(int)> func2 = lambda_func;assert( func1 == func2 );
- Prologue
- Y combinator and deducing this lambda functions via factory bind_ylambda()
- Stateless lambda receiving explicit this object parameter reference used for recursion using
auto
function<int(int)> f = function<int(int)>::bind_ylambda( [](auto& self, int x) -> int {if( 0 == x ) {return 1;} else {return x * self(x-1); // recursion, calling itself w/o explicitly passing `self`}} );assert( 24 == f(4) ); // `self` is bound to function<int(int)>::delegate_type `f.target`, `x` is 4 - or using explicit
function<R(A...)>::delegate_type
function<int(int)> f = function<int(int)>::bind_ylambda( [](function<int(int)>::delegate_type& self, int x) -> int {if( 0 == x ) {return 1;} else {return x * self(x-1); // recursion, calling itself w/o explicitly passing `self`}} );assert( 24 == f(4) ); // `self` is bound to function<int(int)>::delegate_type `f.target`, `x` is 4
- Stateless lambda receiving explicit this object parameter reference used for recursion using
- Lambda alike capture by-reference to value via constructor and jau::bind_capref()
- Prologue
struct big_data {int sum;};big_data data { 0 };typedef bool(*cfunc)(big_data*, int); // to force non-capturing lambda into a free function template type deduction
- Constructor function(I* data_ptr, R(*func)(I*, A…))
function<int(int)> func(&data,(cfunc) ( [](big_data* data, int v) -> bool {stats_ptr->sum += v;return 0 == v;} ) );
- Factory
jau::bind_capref(I* data_ptr, R(*func)(I*, A...))
(cfunc) ( [](big_data* data, int v) -> bool {stats_ptr->sum += v;return 0 == v;} ) );
- Prologue
- Lambda alike capture by-copy of value via constructor and jau::bind_capval()
- Constructor copy function(const I& data, R(*func)(I&, A…))
- Constructor move function(I&& data, R(*func)(I&, A…))
- Factory copy
jau::bind_capval(const I& data, R(*func)(I&, A...))
- Factory move
jau::bind_capval(I&& data, R(*func)(I&, A...))
See example of Capture by-reference to value above.
- std::function function via constructor and jau::bind_std()
- Prologue
std::function<bool(int)> func_stdlambda = [](int i)->bool {return 0 == i;};
- Constructor function(uint64_t id, std::function<R(A…)> func)
jau::function<bool(int)> func(100, func_stdlambda);
- Factory
jau::bind_std(uint64_t id, std::function<R(A...)> func)
- Prologue
Function Limitations
Non unique lambda type names without RTTI using gcc
or non clang
compiler
Due to limitations of jau::make_ctti<R, L, A…>(), not using RTTI on gcc
or non clang
compiler will erroneously mistake different lambda functions defined within one function and using same function prototype R<A...>
to be the same.
jau::type_info::limited_lambda_id will expose the potential limitation.
See CTTI lambda name limitations and limitations of jau::type_info.