Skip to content

About

A lightweight and heap-free polymorphic function wrapper collection. (similar to std::function)

Topics

Resources

Contributing

Stars

19 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

90 Commits

Folders and files

Repository files navigation

Embedded Function

Version - 2.5.1 License - MIT C++ - 11/14/17/20/23/26

gcc-5.1~16.2 - support clang-3.7~23.1 - support msvc-19.10~19.51 - support

A lightweight and heap-free polymorphic function wrapper collection.

πŸ“– Table of Contents

πŸ“Œ Overview

Embedded Function provides a set of no-heap-allocation polymorphic function wrappers implemented based on the C++11 standard, optimized for resource-constrained, low-latency and high-performance environments.

In a single header file, five function wrappers are provided as follows:

namespace ebd {
// Owning polymorphic copyable function wrapper.
template <class Signature, size_t BufferSize = /*DefaultSize*/, size_t Alignment = /*DefaultAlignment*/>
  using fn = basic_fn</*...*/>;

// Owning move-only polymorphic function wrapper.
template <class Signature, size_t BufferSize = /*DefaultSize*/, size_t Alignment = /*DefaultAlignment*/>
  using unique_fn = basic_fn</*...*/>;

// Classic owning polymorphic function wrapper. (like `std::function`)
template <class Signature, size_t BufferSize = /*DefaultSize*/, size_t Alignment = /*DefaultAlignment*/>
  using classic_fn = basic_fn</*...*/>;

// Non-owning polymorphic function wrapper without empty state.
template <class Signature, size_t /*Unused*/ = 0, size_t /*Unused*/ = 0>
  using fn_ref = basic_fn</*...*/>;

// Basic polymorphic function wrapper.
template <class Signature, size_t BufferSize, size_t Alignment,
          bool IsCopyable, bool IsView, bool IsThrowing, bool AssertObjectNoThrow>
  using basic_fn = detail::function</*...*/>;
}

⚑ Quick start

Embedded Function provides all components in one header file embed_function.hpp, so you can copy the file or download header_only.zip, and then add the file (along with embed/) to your include directory.

Alternatively, CMake integration is also available.

#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.

πŸ”§ Wrapper definition syntax

/// The definition of method of a function wrapper is as follows:
        FnWrapper <void(int) const, sizeof(int(*)()), alignof(int(*)())> fn_ = +[](int) {};
//          ^       ^   ^~~    ^    ^~~~~~~~~~~~      ^~~~~~~~~~~~~            ^~~~~~~~~~~
//          |       |   |      |    |                 |                        |
// Function wrapper |   |      |    |                 |                        |
// Return type ~~~~~|   |      |    |                 |                        |
// Parameters ~~~~~~~~~~|      |    |                 |                        |
// Qualifier ~~~~~~~~~~~~~~~~~~|    |                 |                        |
// Buffer size ~~~~~~~~~~~~~~~~~~~~~|                 |                        |
// Alignment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|                        |
// Callable object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
  • Function wrapper: One of ebd::fn, ebd::unique_fn, ebd::classic_fn and ebd::fn_ref.

  • Return type: A type that can be implicitly converted from the direct return type of Callable object.

  • Parameters: Types that can implicitly converts to the parameter types of Callable object.

  • Qualifier: Applies to the wrapper's operator() (e.g., const, noexcept, &, &&), restricting which callable objects can be stored.

  • Buffer size: Size (in bytes) of the internal storage. Triggers static_assert if insufficient - no heap allocation.

  • Alignment: Alignment (in bytes) of the internal storage. Triggers static_assert if 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.

🧠 Design goals driving the design

  • 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() and target_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.
  • Learn and refer to the optimization experience of std::function in libc++, libstdc++, MSVC STL.

  • Provide a view or reference to the callable object, referring to the std::function_ref P0792.

  • Following the above design goals, ebd::fn, ebd::unique_fn, ebd::classic_fn and ebd::fn_ref were designed for developers to use.

✨ Core function wrappers

Summary table

Wrapper Type Copyable View (Non-owning) Throws on Empty Call Assert move/copy Ctor noexcept 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

Key takeaways

  1. Ownership & Copy: fn/classic_fn own callables (copyable), unique_fn owns but is move-only, fn_ref is non-owning (view).

  2. Exception Behavior: fn/unique_fn terminate on empty calls (no exceptions); classic_fn throws std::bad_function_call (like std::function).

  3. Buffer Configuration: fn/unique_fn/classic_fn support configurable buffer sizes (aligned), while fn_ref uses a fixed buffer (unused template param).

  4. Triviality: fn_ref is trivially copyable (same as std::function_ref).

Convertibility

  • 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

Memory layout overview

Owning wrapper: fn, unique_fn, classic_fn.

Non-Owning wrapper: fn_ref.

graph TB;
  subgraph "Non-Owning wrapper"
    B2["Buffer (Fixed)"]
    subgraph CT1["Command Table"]
      I2["Invoker"]
    end
  end

  subgraph "Owning wrapper"
    B1["Buffer (Configurable)"]
    subgraph CT2["Command Table"]
      M1["Manager"]
      I1["Invoker"]
    end
  end
Loading

🧩 Automatic deduction

Brief introduction

In order to simplify the use of ebd::fn, function ebd::make_fn() is provided, which can automatically deduce the signature, buffer size and alignment of the callable object and create a ebd::fn or ebd::fn_ref 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_if will be used instead.

Usage

  • [] means optional.
  • Signature: The signature of the callable object. (such as void(int))
  • BufferSize: The buffer size of the internal storage. (in bytes)
  • Alignment: The alignment of the internal storage. (in bytes)
  • FnWrapper: One of ebd::fn, ebd::unique_fn, ebd::classic_fn and ebd::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[, Alignment]]>();
auto f = ebd::make_fn<Signature[, BufferSize[, Alignment]]>(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 (or ebd::unique_fn when the bound object is move-only) 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);
auto f = ebd::make_fn(std::cw<&Class::member_function>, std::in_place_type<Class>, CArgs...);
auto f = ebd::make_fn(std::cw<&Class::member_function>, std::in_place_type<Class>, {/*std::initializer_list*/}, CArgs...);

πŸ”— Back to function pointer

Brief introduction

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 which has exactly same signature with the wrapper, this mechanism returns the pointer; otherwise, it returns nullptr. Basically, it is equivalent to a highly restricted target() method.

After converting from one wrapper type to another (e.g., from ebd::fn to ebd::unique_fn, or from ebd::fn<int(), 16> to ebd::fn<int(), 32>), operator* on the resulting wrapper returns nullptr.

Example

void free_function() {}
struct Functor { void operator()() {} };

ebd::fn<void()> f = &free_function;
assert(*f == &free_function);

ebd::fn<void(), 64> f_big = f;
assert(*f_big == nullptr);

f = +[]() { /* ... */ }; // lambda -> function pointer
assert(*f != nullptr);

f = []() { /* ... */ };
assert(*f == nullptr);

f = Functor{};
assert(*f == nullptr);

πŸ“¦ C++20 Module support

Brief introduction

Embedded Function provides support for C++20 modules. You can wrap the library into a module according to the guide below.

Usage

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();
}

πŸ› οΈ Debug diagnostics hook

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_fn holding no target) with message: "Empty function has been called!".
  • An internal assertion fails (e.g., constructing ebd::fn_ref from a nullptr function or object pointer). For assertions, the message also appends the failed expression, e.g., [(function_ptr != nullptr) == false], and std::terminate() is called afterwards.

Notes:

  • All diagnostics are compiled out entirely in optimized builds (when __OPTIMIZE__ or NDEBUG is defined), which means zero runtime overhead. Defining DEBUG forces 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_MACROS is defined, EMBED_FN_HOOK_DEBUG is undefined at the end of the header.

βœ… Compatibility

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)

πŸ§ͺ Test

Go to the <root>/test/ directory, and follow the instructions in test/README.md to run the tests.

πŸš€ Performance optimization

Branch elimination

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.

Smart forwarding

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.

Zero-stack overhead

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.

Stateless elimination

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., trivially copyable classes with static operator()). This reduces memory access operations and improves cache efficiency.

Click x64-msvc, rv32-gcc, arm32-gcc and x64-gcc to see more details.

⏱️ Benchmark

( OS: Ubuntu-26.04; Compiler: GCC-16; Standard: C++26; Config: -O2; Tool: iboB/picobench )

Comparison Table (Dimension = 1,000,000)

Benchmark case std ebd fu2 pro std/ebd
AssignmentBenchmark
.CopyAssignmentSmallTrivial
27.229ms 27.528ms 24.359ms 21.148ms 0.99x
AssignmentBenchmark
.CopyAssignmentStateless
33.609ms 4.977ms 9.322ms 8.704ms 6.75x
AssignmentBenchmark
.CopyAssignmentSameType
27.060ms 21.937ms 26.496ms 29.851ms 1.23x
CreateBenchmark
.CaptureLambda
4.043ms 2.484ms 4.352ms 3.417ms 1.63x
CreateBenchmark
.NonTrivialFunctor
13.683ms 2.176ms 4.044ms 3.110ms 6.29x
FreeFunction
.ScalarParameters
5.904ms 3.106ms 5.905ms 6.213ms 1.90x
Functor
.ScalarParameters
3.415ms 0.722ms 3.106ms 3.723ms 4.73x
MoveOnlyFunction
.Params.Array
0.617ms 0.617ms 1.858ms 2.174ms 1.00x
StdOperatorWrapper
.FunctionWrapperAsParams
9.329ms 5.600ms 12.133ms 9.020ms 1.67x

raw data: actions/runs/38058855633

See here for more benchmark results. Follow benchmark/README.md to run the benchmark in your platform.

πŸ—οΈ CMake integration

Make sure CMake 3.14+ is installed, and use:

include(FetchContent)

FetchContent_Declare(
  embedded_function
  GIT_REPOSITORY https://github.com/Kim-J-Smith/Embedded-Function.git
  GIT_TAG        v2.5.1
  GIT_SHALLOW    TRUE
)

FetchContent_MakeAvailable(embedded_function)
target_link_libraries(${YOUR_TARGET_NAME} PRIVATE embedded_function::functions)

Alternatively, after installing with cmake -B build && cmake --install build, use:

find_package(embedded_function CONFIG REQUIRED)
target_link_libraries(${YOUR_TARGET_NAME} PRIVATE embedded_function::functions)

🧭 Future learning & evolution reference

πŸ“š Similar implementations

About

A lightweight and heap-free polymorphic function wrapper collection. (similar to std::function)

Topics

Resources

Contributing

Stars

19 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages