Embedded std::function alternative: lightweight, deterministic, heap-free.
Embedded Function is an embedded-friendly lightweight function wrapper implemented based on the C++11 standard, tailored specifically for embedded systems.
While functionally and conceptually analogous to std::function, it offers substantially reduced overhead and superior real-time performance characteristics. Notably, Embedded Function eliminates dynamic heap memory allocations entirely, ensuring deterministic execution behavior and predictable real-time performance for embedded applications.
In only one header file, 4 function wrappers are provided as follows:
namespace ebd {
// Wrapper for copyable callable objects.
template <class Signature, size_t BufferSize = DefaultSize> fn;
// Wrapper for movable, especially move-only callable objects.
template <class Signature, size_t BufferSize = DefaultSize> unique_fn;
// Wrapper for copyable callable objects which assert no-throw in Ctor and Dtor.
template <class Signature, size_t BufferSize = DefaultSize> safe_fn;
// View (aka reference) for callable objects.
template <class Signature, size_t Unused = 0> fn_ref;
}-
Clone the repository or download the
header_only.zipin the "Release". -
Add include path
<repo_root>/include. -
In program
#include "embed/embed_function.hpp". -
Use the
ebd::fntemplate class.
#include <iostream>
#include "embed/embed_function.hpp"
struct Example {
static void static_mem_fn(int n) { std::cout << "Calling with number: " << n << "\n"; }
void mem_fn(int n) const { std::cout << "Calling with number: " << n << "\n"; }
void operator()(int n) { std::cout << "Calling with number: " << n << "\n"; }
};
auto main() -> int {
Example e;
ebd::fn<void(int)> fn_;
fn_ = &Example::static_mem_fn;
fn_(123); // Prints "Calling with number: 123"
fn_ = [e](int arg) { e.mem_fn(arg); };
fn_(456); // Prints "Calling with number: 456"
fn_ = e;
fn_(789); // Prints "Calling with number: 789"
}/// The definition of method of a function wrapper is as follows:
ebd::fn<int (int, float, char) const, 3*sizeof(void*)> fn_;
// ^ ^ ^ ^ ^ ^
// | | | | | |
// Return type | | | | |
// Parameters ~|~~~~~|~~~~~| | |
// Qualifier ~~~~~~~~~~~~~~~~~~~~| |
// Buffer size ~~~~~~~~~~~~~~~~~~~~~~~~~~~|The
Qualifieris used to restrict the callable objects wrapped withinebd::fn, rather thanebd::fnitself. In other words, theoperator()of theebd::fnobject will be qualified with theQualifiermodifier.
The
Buffer sizeis the size used to store the callable object, which can be omitted. If omitted, this parameter will be set todetail::default_buffer_size::valueby default, which is sufficient to store most common callable objects, including function pointers, simple non-capturing and capturing lambdas, and lightweight custom classes.
-
Should behave close to a normal function pointer. Small, efficient, no heap allocation.
-
Support the packaging of all callable objects in C++, including:
- Free function.
- Lambda function.
- Functor.
- Static member function.
- Member function.
-
Be usable with C++11 while offering more functionality for later editions.
-
Be constexpr and exception friendly. As much as possible should be declared constexpr and noexcept.
-
Should be based on the analysis of N4159, P2548 and LWG2393, and should avoid repeating the mistakes made by
std::function. Therefore, Embedded Function should:- NOT implement the method
target()andtarget_type(). - Allow the application of qualifiers, such as
const,volatile,&and&&, to the function signature. - Ensure that the qualifier of the underlying object is consistent or more restrictive than that of the function signature.
- NOT implement the method
-
Learn and refer to the optimization experience of
std::functionin libstdc++, libc++ and Microsoft C++ Standard Library. -
Provide a view or reference to the callable object, referring to the
std::function_refP0792. -
Following the above design goals,
ebd::fn,ebd::unique_fn,ebd::safe_fnandebd::fn_refwere designed for developers to use.
| Wrapper Type | Copyable | View (Non-owning) | Throws on Empty Call | Assert No-Throw (Ctor/Dtor) | Buffer Size | Primary Use Case |
|---|---|---|---|---|---|---|
ebd::fn |
Yes | No | Yes (std::bad_function_call) |
No | Configurable (aligned, default: sizeof(void(Class::*)())) |
Copyable callable wrapper |
ebd::unique_fn |
No | No | Yes (std::bad_function_call) |
No | Configurable (aligned, default: sizeof(void(Class::*)())) |
Move-only callable wrapper |
ebd::safe_fn |
Yes | No | No (std::terminate()) |
Yes | Configurable (aligned, default: sizeof(void(Class::*)())) |
Exception-safe copyable callable wrapper |
ebd::fn_ref |
Yes | Yes | No (NO EMPTY STATE) | No | Fixed | Lightweight non-owning reference(view) of callables |
ebd::basic_fn |
- | - | - | - | - | Customized by the user |
-
Ownership & Copy:
fn/safe_fnown callables (copyable),unique_fnowns but is move-only,fn_refis non-owning (view). -
Exception Behavior: Only
fn/unique_fnthrow on empty calls;safe_fn/fn_refterminate (no exceptions). -
Buffer Configuration:
fn/unique_fn/safe_fnsupport configurable buffer sizes (aligned), whilefn_refuses a fixed buffer (unused template param). -
Triviality:
fn_refis trivially copyable (same asstd::function_ref).
ebd::fn / ebd::unique_fn / ebd::safe_fn / ebd::fn_ref completely eliminate runtime checks for empty function states during invocation, significantly boosting performance of frequent function calls.
ebd::fn / ebd::unique_fn / ebd::safe_fn / ebd::fn_ref enable scalar arguments and small-sized trivial arguments to be passed via registers instead of having to be passed via the stack as in std::function. This significantly reduces the memory access overhead during parameter passing.
ebd::fn_ref occupies no stack space when used as a function parameter; it is passed entirely in registers. This allows the compiler to directly tail-call the wrapped target, removing the cost of an extra stack frame. See x86_64-asm.
ebd::fn / ebd::unique_fn / ebd::safe_fn / ebd::fn_ref do not store the functor or its pointer if the functor is stateless (e.g., empty classes with trivial operations). This reduces memory access operations and improves cache efficiency.
In order to simplify the use of ebd::fn, function ebd::make_fn() is provided, which can automatically deduce the signature and buffer size of the callable object and create a ebd::fn or ebd::unique_fn object. (Return ebd::unique_fn only when the callable object is of the move-only type.)
NOTE: The Concepts language feature is available for use provided that the compiler is configured to support the C++20 standard. On platforms that do not support C++20,
enable_ifwill be used instead.
[]means optional.Signature: The signature of the callable object. (such asvoid(int))BufferSize: The buffer size of the callable object. (such as2*sizeof(void*))
// Create empty ebd::fn with specified signature and buffer size.
// If the BufferSize is omitted, it will be set by default (usually 2*sizeof(void*)).
auto f = ebd::make_fn<Signature[, BufferSize]>();
auto f = ebd::make_fn<Signature[, BufferSize]>(nullptr);// Create ebd::fn or ebd::unique_fn from unambiguous callable object.
// If the Signature is omitted, the signature will be deduced from Callable_Object.
auto f = ebd::make_fn[<Signature>](Callable_Object);// Create ebd::fn or ebd::unique_fn from ambiguous callable object with specified signature, such as overload free function, overload member function, etc.
auto f = ebd::make_fn<Signature>(Ambiguous_Callable_Object);// Create specified function wrapper and automatically deduce the template arguments.
// The Callable_Object should be unambiguously callable (non-overload).
auto f = ebd::make_fn<ebd::fn>(Callable_Object);
auto f = ebd::make_fn<ebd::unique_fn>(Callable_Object);
auto f = ebd::make_fn<ebd::safe_fn>(Callable_Object);
auto f = ebd::make_fn<ebd::fn_ref>(Callable_Object);// In place build functor within buffer. Functor should be unambiguously callable (non-overload).
auto f = ebd::make_fn(std::in_place_type<Functor>, CArgs...); // Since C++17
auto f = ebd::make_fn(std::in_place_type<Functor>, {/*std::initializer_list*/}, CArgs...); // Since C++17In embedded MCU development, it is often necessary to pass a C-style free function pointer as an argument, as existing libraries are typically written in C. To address this, we have implemented an operator* overload that simplifies converting an object of type ebd::fn / ebd::unique_fn / ebd::safe_fn / ebd::fn_ref to a C-style free function pointer.
If the object encapsulated by the function wrapper is a valid function pointer, this mechanism returns the pointer; otherwise, it returns nullptr. Basically, it is equivalent to a highly restricted target() method.
void free_function() {}
struct Functor { void operator()() {} };
ebd::fn<void()> fn_ = &free_function;
void(*free_function_pointer)() = *fn_;
ASSERT_EQ(free_function_pointer, &free_function);
fn_ = +[]() { /* ... */ }; // lambda -> function pointer
free_function_pointer = *fn_;
ASSERT_NE(free_function_pointer, nullptr); // NOT equal nullptr
fn_ = []() { /* ... */ };
free_function_pointer = *fn_;
ASSERT_EQ(free_function_pointer, nullptr);
fn_ = Functor{};
free_function_pointer = *fn_;
ASSERT_EQ(free_function_pointer, nullptr);Embedded Function provides support for C++20 modules. You can wrap the library into a module according to the guide below.
To create a module named ebd.function, create a module interface file (e.g., ebd_function.cppm or ebd_function.ixx):
module;
#include "embed/embed_function.hpp"
export module ebd.function;
export namespace ebd {
using ::ebd::basic_fn;
using ::ebd::fn;
using ::ebd::unique_fn;
using ::ebd::safe_fn;
using ::ebd::fn_ref;
using ::ebd::make_fn;
}Then you can use it in other files:
import ebd.function;
auto main() -> int {
ebd::fn<void()> fn1 = []() { /* ... */ };
ebd::unique_fn<void()> fn2 = []() { /* ... */ };
ebd::safe_fn<void()> fn3 = []() { /* ... */ };
ebd::fn_ref<void()> fn4 = fn2;
auto fn5 = ebd::make_fn([]() { /* ... */ });
fn1(); fn2(); fn3(); fn4(); fn5();
}Every compiler with modern C++11 support should work. Embedded Function only depends on the standard library.
- GCC 5.1+
- Clang 3.7+
- MSVC v19.20+ (VS16.0+)
Go to the <root>/test/ directory, and follow the instructions in HOW-TO-TEST.md to run the tests.
Go to the <root>/benchmark/ directory, and follow the instructions in HOW-TO-BENCHMARK.md to run the tests.
( Compiler:
MSVCStandard:C++14Config:ReleaseTool: picobench )
std:
std::function, ebd:ebd::fn, fu2:fu2::function
## FreeFunction.ScalarParameters:
Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second
--------------------------|--------:|----------:|--------:|-------:|----------:
free_scalar_std * | 10000 | 0.030 | 3 | - |332225913.6
free_scalar_ebd | 10000 | 0.028 | 2 | 0.930 |357142857.1
free_scalar_fu2 | 10000 | 0.052 | 5 | 1.731 |191938579.7
free_scalar_std * | 100000 | 0.301 | 3 | - |332667997.3
free_scalar_ebd | 100000 | 0.265 | 2 | 0.881 |377643504.5
free_scalar_fu2 | 100000 | 0.523 | 5 | 1.742 |191021967.5
free_scalar_std * | 1000000 | 3.006 | 3 | - |332712270.4
free_scalar_ebd | 1000000 | 2.708 | 2 | 0.901 |369317132.6
free_scalar_fu2 | 1000000 | 5.264 | 5 | 1.751 |189958778.9
## FreeFunction.TrivialParameters:
Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second
--------------------------|--------:|----------:|--------:|-------:|----------:
free_trivial_std * | 10000 | 0.032 | 3 | - |311526479.8
free_trivial_ebd | 10000 | 0.024 | 2 | 0.754 |413223140.5
free_trivial_fu2 | 10000 | 0.052 | 5 | 1.626 |191570881.2
free_trivial_std * | 100000 | 0.322 | 3 | - |310366232.2
free_trivial_ebd | 100000 | 0.240 | 2 | 0.746 |415800415.8
free_trivial_fu2 | 100000 | 0.510 | 5 | 1.583 |196001568.0
free_trivial_std * | 1000000 | 3.222 | 3 | - |310375865.2
free_trivial_ebd | 1000000 | 2.508 | 2 | 0.778 |398692289.3
free_trivial_fu2 | 1000000 | 5.792 | 5 | 1.798 |172660876.8
## FreeFunction.CopyHardParameters:
Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second
--------------------------|--------:|----------:|--------:|-------:|----------:
free_copyhard_std * | 10000 | 0.197 | 19 | - | 50684237.2
free_copyhard_ebd | 10000 | 0.198 | 19 | 1.004 | 50505050.5
free_copyhard_fu2 | 10000 | 0.303 | 30 | 1.537 | 32981530.3
free_copyhard_std * | 100000 | 1.976 | 19 | - | 50604726.5
free_copyhard_ebd | 100000 | 1.982 | 19 | 1.003 | 50456632.5
free_copyhard_fu2 | 100000 | 3.044 | 30 | 1.541 | 32849352.9
free_copyhard_std * | 1000000 | 19.898 | 19 | - | 50256307.2
free_copyhard_ebd | 1000000 | 20.052 | 20 | 1.008 | 49870088.4
free_copyhard_fu2 | 1000000 | 31.358 | 31 | 1.576 | 31889890.6
## FreeFunction.CallTrivialParameters:
Name (* = baseline) | Dim | Total ms | ns/op |Baseline| Ops/second
--------------------------|--------:|----------:|--------:|-------:|----------:
free_calltrivial_std * | 10000 | 0.032 | 3 | - |311526479.8
free_calltrivial_ebd | 10000 | 0.024 | 2 | 0.751 |414937759.3
free_calltrivial_fu2 | 10000 | 0.056 | 5 | 1.757 |177304964.5
free_calltrivial_std * | 100000 | 0.320 | 3 | - |312597686.8
free_calltrivial_ebd | 100000 | 0.257 | 2 | 0.802 |389711613.4
free_calltrivial_fu2 | 100000 | 0.584 | 5 | 1.827 |171115674.2
free_calltrivial_std * | 1000000 | 3.223 | 3 | - |310269934.8
free_calltrivial_ebd | 1000000 | 2.407 | 2 | 0.747 |415506710.4
free_calltrivial_fu2 | 1000000 | 5.934 | 5 | 1.841 |168517551.1See here for more benchmark results.