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, five function wrappers are provided as follows (the customizable ebd::basic_fn is the fifth):
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 classic_fn; // Wrapper for copyable callable objects (throws on empty, like std::function).
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"
}More examples are available in the
example/directory.
/// 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::classic_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. 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 libc++, libstdc++, MSVC STL. -
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::classic_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 | No (std::terminate()) |
No | Configurable (aligned, default: sizeof(void(Class::*)())) |
Copyable callable wrapper |
ebd::unique_fn |
No | No | No (std::terminate()) |
No | Configurable (aligned, default: sizeof(void(Class::*)())) |
Move-only callable wrapper |
ebd::classic_fn |
Yes | No | Yes (std::bad_function_call) |
No | Configurable (aligned, default: sizeof(void(Class::*)())) |
Classic wrapper (like std::function) |
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/classic_fnown callables (copyable),unique_fnowns but is move-only,fn_refis non-owning (view). -
Exception Behavior:
fn/unique_fnterminate on empty calls (no exceptions);classic_fnthrowsstd::bad_function_call(likestd::function). -
Buffer Configuration:
fn/unique_fn/classic_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::classic_fn |
ebd::fn_ref |
|---|---|---|---|---|
ebd::fn |
Yes-D | Yes-D | Yes-I | Yes-R |
ebd::unique_fn |
No | Yes-D | No | Yes-R |
ebd::classic_fn |
Yes-I | Yes-I | Yes-D | Yes-R |
ebd::fn_ref |
Yes-I | Yes-I | Yes-I | Yes-D |
Owning wrapper:
fn,unique_fn,classic_fn.
Non-Owning wrapper:
fn_ref.
graph TB;
subgraph "Non-Owning wrapper"
B2["Buffer (Fixed)"]
I2["Invoker"]
end
subgraph "Owning wrapper"
B1["Buffer (Configurable)"]
M1["Manager"]
I1["Invoker"]
end
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, ebd::unique_fn or ebd::fn_ref object. (Return ebd::unique_fn only when the callable object is of the move-only type. Return ebd::fn_ref only when the callable object is std::cw.)
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*))FnWrapper: One ofebd::fn,ebd::unique_fn,ebd::classic_fnandebd::fn_ref.
// 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::classic_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).
// Since C++17.
auto f = ebd::make_fn[<FnWrapper[, Signature]>](std::in_place_type<Functor>, CArgs...);
auto f = ebd::make_fn[<FnWrapper[, Signature]>](
std::in_place_type<Functor>, {/*std::initializer_list*/}, CArgs...);// Create ebd::fn_ref from std::constant_wrapper.
// Since C++26
auto f = ebd::make_fn(std::cw<&free_function>);
auto f = ebd::make_fn(std::cw<&Class::member_function>, obj);
auto f = ebd::make_fn(std::cw<&Class::member_function>, &obj);In 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::classic_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::classic_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::classic_fn<void()> fn3 = []() { /* ... */ };
ebd::fn_ref<void()> fn4 = fn2;
auto fn5 = ebd::make_fn([]() { /* ... */ });
fn1(); fn2(); fn3(); fn4(); fn5();
}EMBED_FN_HOOK_DEBUG(message) is a user-defined macro hook for capturing diagnostic output in debug builds. Define it before including the header:
#include <cstdio>
#define EMBED_FN_HOOK_DEBUG(message) fputs(message, stderr)
#include "embed/embed_function.hpp"The library invokes the hook with a pre-formatted message that contains the source location:
<file>:<line>:
<message>
The hook is called when:
- An empty wrapper is invoked (e.g.,
ebd::fn/ebd::unique_fnholding no target) with message:"Empty function has been called!". - An internal assertion fails (e.g., constructing
ebd::fn_reffrom anullptrfunction or object pointer). For assertions, the message also appends the failed expression, e.g.,[(function_ptr != nullptr) == false], andstd::terminate()is called afterwards.
Notes:
- All diagnostics are compiled out entirely in optimized builds (when
__OPTIMIZE__orNDEBUGis defined), which means zero runtime overhead. DefiningDEBUGforces the diagnostics to stay active even in optimized builds. - If the hook is not defined, the library substitutes a no-op. The checks still run in debug builds, and failed internal assertions still call
std::terminate()regardless of the hook. - When
EMBED_FN_CONFIG_UNDEF_MACROSis defined,EMBED_FN_HOOK_DEBUGis undefined at the end of the header.
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 test/README.md to run the tests.
ebd::fn / ebd::unique_fn / ebd::classic_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::classic_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::classic_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-16Standard: C++23Config: -O2Tool: iboB/picobench )
- std: Standard Template Library
- ebd: Embedded-Function
- fu2: Naios/function2
- pro: ngcpp/proxy
| Name (* = baseline) | Dim | Total ms | ns/op | Baseline | Ops/second |
|---|---|---|---|---|---|
functor_trivial_std * |
1000 | 0.004 | 3 | - | 250878073.3 |
functor_trivial_ebd |
1000 | 0.001 | 0 | 0.231 | 1085776330.1 |
functor_trivial_fu2 |
1000 | 0.003 | 3 | 0.829 | 302571860.8 |
functor_trivial_pro |
1000 | 0.003 | 3 | 0.804 | 312012480.5 |
functor_trivial_std * |
1000000 | 3.858 | 3 | - | 259225310.3 |
functor_trivial_ebd |
1000000 | 0.865 | 0 | 0.224 | 1155748694.0 |
functor_trivial_fu2 |
1000000 | 3.157 | 3 | 0.818 | 316715483.1 |
functor_trivial_pro |
1000000 | 3.158 | 3 | 0.819 | 316677871.8 |
See here for more benchmark results. Follow
benchmark/README.mdto run the benchmark in your platform.