Skip to content

Kim-J-Smith/Embedded-Function

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

40 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Embedded Function

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

gcc-C++11~23 - support clang-C++11~23 - support msvc-C++14~23 - support

A lightweight and heap-free polymorphic function wrapper collection.

πŸ“Œ Overview

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

⚑ Quick start

  • Clone the repository or download the header_only.zip in the "Release".

  • Add include path <repo_root>/include.

  • In program #include "embed/embed_function.hpp".

  • Use the ebd::fn template 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"
}

πŸ”§ Wrapper definition syntax

/// 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 of ebd::fn, ebd::unique_fn, ebd::safe_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. Defaults to DefaultSize. 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 libstdc++, libc++ and Microsoft C++ Standard Library.

  • 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::safe_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 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

Key takeaways

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

  2. Exception Behavior: Only fn/unique_fn throw on empty calls; safe_fn/fn_ref terminate (no exceptions).

  3. Buffer Configuration: fn/unique_fn/safe_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::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

🧩 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 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_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 callable object. (such as 2*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++17

πŸ”— 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::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.

Example

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

πŸ“¦ 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::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();
}

βœ… 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 HOW-TO-TEST.md to run the tests.

πŸš€ Performance optimization

Branch elimination

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.

Smart forwarding

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.

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

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

⏱️ Benchmark

Embedded-Function has 5%~30% performance enhancement over std::function.

( Compiler: GCC-14 Standard: C++14 Config: -Os Tool: picobench fu2: function2 )

StdOperatorWrapper.FunctionWrapperAsParams:

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.md to run the benchmark in your platform.

🧭 Future learning & evolution reference

πŸ“š Similar implementations

About

A lightweight and heap-free polymorphic function wrapper collection.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages