A lightweight and heap-free polymorphic function wrapper collection.
Embedded Function is a lightweight and no-heap-allocation function wrapper collection implemented based on the C++11 standard, optimized(see below) for resource-constrained or high-performance environments.
The library is freestanding, making it feasible for embedded development or kernel design of an operating system.
In a single header file, four function wrappers are provided as follows:
namespace ebd {
template <class Signature, size_t BufferSize = DefaultSize>
class fn; // Wrapper for copyable callable objects.
template <class Signature, size_t BufferSize = DefaultSize>
class unique_fn; // Wrapper for movable, especially move-only callable objects.
template <class Signature, size_t BufferSize = DefaultSize>
class safe_fn; // Wrapper for copyable callable objects which assert no-throw in Ctor and Dtor.
template <class Signature, size_t Unused = 0>
class fn_ref; // View (non-owning wrapper) for callable objects.
}-
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:
FnWrapper <void(int, char) const, 3*sizeof(void*)> fn_ = +[](int, char) {};
// ^ ^ ^~~~~~~ ^ ^~~~~~ ^~~~~~~~~~~~~
// | | | | | |
// Function wrapper | | | | |
// Return type ~~~~~| | | | |
// Parameters ~~~~~~~~~~| | | |
// Qualifier ~~~~~~~~~~~~~~~~~~~~~~~~| | |
// Buffer size ~~~~~~~~~~~~~~~~~~~~~~~~~~~~| |
// Callable object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|-
Function wrapper: One ofebd::fn,ebd::unique_fn,ebd::safe_fnandebd::fn_ref. -
Return type: A type that can be implicitly converted from the direct return type ofCallable object. -
Parameters: Types that can implicitly converts to the parameter types ofCallable object. -
Qualifier: Applies to the wrapper'soperator()(e.g.,const,noexcept,&,&&), restricting which callable objects can be stored. -
Buffer size: Size (in bytes) of the internal storage. Defaults toDefaultSize. Triggersstatic_assertif insufficient - no heap allocation. -
Callable object: Any entity callable with the target signature (function pointer, lambda, function object,std::reference_wrapper). Copied or moved into the buffer depending on wrapper type.
-
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).
Yes-D: Convertible and direct wrapping (To.BufferSize>=From.BufferSize);Yes-I: Convertible and indirect wrapping (To.BufferSize>=sizeof(From));Yes-R: Convertible and non-owning wrapping.No: Inconvertible
| From \ To | ebd::fn |
ebd::unique_fn |
ebd::safe_fn |
ebd::fn_ref |
|---|---|---|---|---|
ebd::fn |
Yes-D | Yes-D | No | Yes-R |
ebd::unique_fn |
No | Yes-D | No | Yes-R |
ebd::safe_fn |
Yes-I | Yes-I | Yes-D | Yes-R |
ebd::fn_ref |
Yes-I | Yes-I | Yes-I | Yes-D |
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) if `Signature` is omitted.
auto f = ebd::make_fn<ebd::fn[, Signature]>(Callable_Object);
auto f = ebd::make_fn<ebd::unique_fn[, Signature]>(Callable_Object);
auto f = ebd::make_fn<ebd::safe_fn[, Signature]>(Callable_Object);
auto f = ebd::make_fn<ebd::fn_ref[, Signature]>(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.10+ (v19.34+ / VS17.4+ recommended)
Go to the <root>/test/ directory, and follow the instructions in HOW-TO-TEST.md to run the tests.
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.
Embedded-Function has 5%~30% performance enhancement over std::function.
(
Compiler: GCC-14Standard: C++14Config: -OsTool: picobenchfu2: function2 )
| Name (* = baseline) | Dim | Total ms | ns/op | Baseline | Ops/second |
|---|---|---|---|---|---|
std::function * |
10000 | 0.090 | 8 | - | 111671952.5 |
fu2::function |
10000 | 0.176 | 17 | 1.968 | 56744349.7 |
ebd::fn |
10000 | 0.068 | 6 | 0.758 | 147412179.2 |
fu2::function_view |
10000 | 0.034 | 3 | 0.379 | 294602875.3 |
ebd::fn_ref |
10000 | 0.034 | 3 | 0.375 | 297424305.5 |
std::function * |
100000 | 0.895 | 8 | - | 111756442.5 |
fu2::function |
100000 | 1.765 | 17 | 1.973 | 56644386.5 |
ebd::fn |
100000 | 0.678 | 6 | 0.758 | 147444347.1 |
fu2::function_view |
100000 | 0.340 | 3 | 0.380 | 294061429.4 |
ebd::fn_ref |
100000 | 0.308 | 3 | 0.345 | 324361494.4 |
std::function * |
1000000 | 9.952 | 9 | - | 100481295.4 |
fu2::function |
1000000 | 17.733 | 17 | 1.782 | 56391833.9 |
ebd::fn |
1000000 | 6.832 | 6 | 0.686 | 146378186.5 |
fu2::function_view |
1000000 | 3.420 | 3 | 0.344 | 292392274.6 |
ebd::fn_ref |
1000000 | 3.249 | 3 | 0.326 | 307826614.8 |
See here for more benchmark results. Follow
HOW-TO-BENCHMARK.mdto run the benchmark in your platform.