Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- 'include/**'
- 'benchmark/**'
- '.github/workflows/benchmark.yml'
workflow_dispatch:

jobs:
# Ubuntu check
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
pull_request:
branches: [ main, version-* ]
types: [ opened, synchronize, reopened ]
workflow_dispatch:

jobs:
# GCC 13
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ To make sure the process of accepting patches goes smoothly for everyone (especi

3. Do your modifications on that branch. Except for special cases, your contribution should include proper unit tests and documentation.

4. Make sure your modifications did not break anything by building and running the [tests](./test/HOW-TO-TEST.md).
4. Make sure your modifications did not break anything by building and running the [tests](./test/README.md).

5. Commit your changes. Your commit message should start with a one line short description of the modifications, with the details and explanations of your modifications following in subsequent paragraphs or bullet points.

Expand Down
40 changes: 20 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<p align="center">
<img src="https://img.shields.io/badge/Version-2.1.11-yellow?style=for-the-badge&logo=github" alt="Version - 2.1.11">
<img src="https://img.shields.io/badge/License-MIT-orange?style=for-the-badge" alt="License - MIT">
<img src="https://img.shields.io/badge/C++-11/14/17/20/23-blue?style=for-the-badge&logo=c%2B%2B" alt="C++ - 11/14/17/20/23">
<img src="https://img.shields.io/badge/C++-11/14/17/20/23/26-blue?style=for-the-badge&logo=c%2B%2B" alt="C++ - 11/14/17/20/23/26">
</p>

<p align="center">
Expand Down Expand Up @@ -32,7 +32,7 @@ template <class Signature, size_t BufferSize = /*DefaultSize*/>
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.
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.
}
Expand Down Expand Up @@ -89,7 +89,7 @@ auto main() -> int {
// Callable object ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~|
```

- *`Function wrapper`*: One of `ebd::fn`, `ebd::unique_fn`, `ebd::safe_fn` and `ebd::fn_ref`.
- *`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`*.

Expand Down Expand Up @@ -126,27 +126,27 @@ auto main() -> int {

- Provide a view or reference to the callable object, referring to the [`std::function_ref` P0792](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p0792r14.html).

- Following the above design goals, `ebd::fn`, `ebd::unique_fn`, `ebd::safe_fn` and `ebd::fn_ref` were designed for developers to use.
- 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 No-Throw (Ctor/Dtor) | Buffer Size | Primary Use Case |
| :----------- | :---: | :---: | :---: | :---: | :---: | :---: |
| [`ebd::fn`](./docs/api/fn.md) | Yes | No | Yes (`std::bad_function_call`) | No | Configurable (aligned, default: `sizeof(void(Class::*)())`) | Copyable callable wrapper |
| [`ebd::unique_fn`](./docs/api/unique_fn.md) | No | No | Yes (`std::bad_function_call`) | No | Configurable (aligned, default: `sizeof(void(Class::*)())`) | Move-only callable wrapper |
| [`ebd::safe_fn`](./docs/api/safe_fn.md) | Yes | No | No (`std::terminate()`) | Yes | Configurable (aligned, default: `sizeof(void(Class::*)())`) | Exception-safe copyable callable wrapper |
| [`ebd::fn`](./docs/api/fn.md) | Yes | No | No (`std::terminate()`) | No | Configurable (aligned, default: `sizeof(void(Class::*)())`) | Copyable callable wrapper |
| [`ebd::unique_fn`](./docs/api/unique_fn.md) | No | No | No (`std::terminate()`) | No | Configurable (aligned, default: `sizeof(void(Class::*)())`) | Move-only callable wrapper |
| [`ebd::classic_fn`](./docs/api/classic_fn.md) | Yes | No | Yes (`std::bad_function_call`) | No | Configurable (aligned, default: `sizeof(void(Class::*)())`) | Classic wrapper (like `std::function`) |
| [`ebd::fn_ref`](./docs/api/fn_ref.md) | Yes | Yes | No (*NO EMPTY STATE*) | No | Fixed | Lightweight non-owning reference(view) of callables |
| [`ebd::basic_fn`](./docs/api/basic_fn.md) | - | - | - | - | - | 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).
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**: Only `fn`/`unique_fn` throw on empty calls; `safe_fn`/`fn_ref` terminate (no exceptions).
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`/`safe_fn` support configurable buffer sizes (aligned), while `fn_ref` uses a fixed buffer (unused template param).
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`).

Expand All @@ -157,11 +157,11 @@ auto main() -> int {
- `Yes-R`: Convertible and non-owning wrapping.
- `No`: Inconvertible

| From \ To | `ebd::fn` | `ebd::unique_fn` | `ebd::safe_fn` | `ebd::fn_ref` |
| From \ To | `ebd::fn` | `ebd::unique_fn` | `ebd::classic_fn` | `ebd::fn_ref` |
| :---: | :---: | :---: | :---: | :---: |
| `ebd::fn` | Yes-D | Yes-D | No | Yes-R |
| `ebd::fn` | Yes-D | Yes-D | Yes-I | Yes-R |
| `ebd::unique_fn` | No | Yes-D | No | Yes-R |
| `ebd::safe_fn` | Yes-I | Yes-I | Yes-D | Yes-R |
| `ebd::classic_fn` | Yes-I | Yes-I | Yes-D | Yes-R |
| `ebd::fn_ref` | Yes-I | Yes-I | Yes-I | Yes-D |

## 🧩 Automatic deduction
Expand Down Expand Up @@ -202,7 +202,7 @@ auto f = ebd::make_fn<Signature>(Ambiguous_Callable_Object);
// 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::classic_fn[, Signature]>(Callable_Object);
auto f = ebd::make_fn<ebd::fn_ref[, Signature]>(Callable_Object);
```

Expand All @@ -216,7 +216,7 @@ auto f = ebd::make_fn(std::in_place_type<Functor>, {/*std::initializer_list*/},

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

Expand Down Expand Up @@ -262,7 +262,7 @@ export namespace ebd {
using ::ebd::basic_fn;
using ::ebd::fn;
using ::ebd::unique_fn;
using ::ebd::safe_fn;
using ::ebd::classic_fn;
using ::ebd::fn_ref;
using ::ebd::make_fn;
}
Expand All @@ -276,7 +276,7 @@ import ebd.function;
auto main() -> int {
ebd::fn<void()> fn1 = []() { /* ... */ };
ebd::unique_fn<void()> fn2 = []() { /* ... */ };
ebd::safe_fn<void()> fn3 = []() { /* ... */ };
ebd::classic_fn<void()> fn3 = []() { /* ... */ };
ebd::fn_ref<void()> fn4 = fn2;
auto fn5 = ebd::make_fn([]() { /* ... */ });

Expand All @@ -301,19 +301,19 @@ Go to the `<root>/test/` directory, and follow the instructions in [`test/README

### 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.
`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::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` / `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](./docs/perf/x86_64_gcc_fn_ref_zero_stack.md).

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

> Click [x64-asm](./docs/perf/x86_64_msvc_asm_analysis.md), [rv32-asm](./docs/perf/riscv_gcc_asm_analysis.md) and [arm32-asm](./docs/perf/arm_gcc_asm_analysis.md) to see more details.

Expand Down
4 changes: 2 additions & 2 deletions docs/api/basic_fn.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ view(42);

## Notes

- Prefer using the predefined aliases (`ebd::fn`, `ebd::unique_fn`, `ebd::safe_fn`, `ebd::fn_view`) unless you need a combination not covered by them.
- Prefer using the predefined aliases (`ebd::fn`, `ebd::unique_fn`, `ebd::classic_fn`, `ebd::fn_ref`) unless you need a combination not covered by them.
- The buffer size is automatically aligned to the nearest alignment boundary.
- If the callable object is too large for the specified buffer size, a `static_assert` will be triggered at compile time.

Expand All @@ -77,5 +77,5 @@ view(42);
- [`ebd::detail::function`](./detail/function.md) - The underlying implementation
- [`ebd::fn`](./fn.md) - For copyable callables
- [`ebd::unique_fn`](./unique_fn.md) - For move-only callables
- [`ebd::safe_fn`](./safe_fn.md) - For exception-safe callables
- [`ebd::classic_fn`](./classic_fn.md) - For callables with `std::bad_function_call` on empty
- [`ebd::fn_ref`](./fn_ref.md) - For non-owning views of callables
73 changes: 73 additions & 0 deletions docs/api/classic_fn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# `ebd::classic_fn`

## Overview

`ebd::classic_fn` is a function object wrapper for copyable and callable objects. It is an alias of `ebd::detail::function` with specific configuration parameters that match the traditional `std::function` behavior: calling an empty wrapper throws `std::bad_function_call`. (if exceptions are enabled)

## Template Parameters

| Parameter | Description |
|-----------|-------------|
| `Signature` | Function signature, e.g., `Ret(Args...)` or `Ret(Args...) const`. |
| `BufferSize` | Buffer size used for storing the callable object. Defaults to `detail::default_buffer_size::value` if omitted. |

## Configuration

`ebd::classic_fn` is configured with the following parameters:

| Configuration | Value | Description |
|---------------|-------|-------------|
| `IsCopyable` | `true` | The callable object must be copyable. |
| `IsView` | `false` | This is not a view; the wrapper owns the callable object. |
| `IsThrowing` | `true` | The wrapper will throw `std::bad_function_call` when called in an empty state. |
| `AssertObjectNoThrow` | `false` | The callable object does not need to be nothrow-constructible or nothrow-destructible. |

## Member Functions

All member functions of `ebd::detail::function` are available for `ebd::classic_fn`. For detailed documentation, see [`ebd::detail::function`](./detail/function.md).

## Usage Examples

### Basic Usage

```cpp
#include "embed/embed_function.hpp"

// Create a classic function wrapper for a void(int) signature
ebd::classic_fn<void(int)> fn;

// Assign a function pointer
void foo(int x) { /* do something */ }
fn = &foo;
fn(42);

// Assign a lambda
fn = [](int x) { /* do something */ };
fn(42);
```

### With Custom Buffer Size

```cpp
// Create a classic function wrapper with a custom buffer size
ebd::classic_fn<void(int), 32> fn; // 32-byte buffer

// Assign a larger lambda with captures
int value = 42;
fn = [value](int x) { /* use value */ };
fn(100);
```

## Notes

- `ebd::classic_fn` is copyable and owns the callable object it wraps.
- The buffer size is automatically aligned to the nearest alignment boundary.
- If the callable object is too large for the specified buffer size, a `static_assert` will be triggered at compile time.
- When called in an empty state, `ebd::classic_fn` will throw `std::bad_function_call` (if exceptions are enabled), matching `std::function` behavior.

## See Also

- [`ebd::detail::function`](./detail/function.md) - The underlying implementation
- [`ebd::fn`](./fn.md) - For copyable callables (terminate on empty)
- [`ebd::unique_fn`](./unique_fn.md) - For move-only callables
- [`ebd::fn_ref`](./fn_ref.md) - For non-owning views of callables
2 changes: 1 addition & 1 deletion docs/api/detail/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ int result = add(10, 20); // result = 30
## Notes
- The `ebd::detail::function` class is not intended for direct use. Instead, use the predefined aliases such as `ebd::fn`, `ebd::unique_fn`, `ebd::safe_fn`, and `ebd::fn_view`.
- The `ebd::detail::function` class is not intended for direct use. Instead, use the predefined aliases such as `ebd::fn`, `ebd::unique_fn`, `ebd::classic_fn`, and `ebd::fn_ref`.
- The buffer size is automatically aligned to the nearest alignment boundary.
- The function wrapper supports all callable objects, including free functions, lambdas, functors, static member functions, and member functions.
- When `Config::isView` is `true`, the wrapper acts as a non-owning view, similar to `std::function_ref`.
6 changes: 3 additions & 3 deletions docs/api/fn.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
|---------------|-------|-------------|
| `IsCopyable` | `true` | The callable object must be copyable. |
| `IsView` | `false` | This is not a view; the wrapper owns the callable object. |
| `IsThrowing` | `true` | The wrapper will throw `std::bad_function_call` when called in an empty state. |
| `IsThrowing` | `false` | The wrapper will call `std::terminate()` when called in an empty state. |
| `AssertObjectNoThrow` | `false` | The callable object does not need to be nothrow-constructible or nothrow-destructible. |

## Member Functions
Expand Down Expand Up @@ -70,11 +70,11 @@ fn(100);
- `ebd::fn` is copyable and owns the callable object it wraps.
- The buffer size is automatically aligned to the nearest alignment boundary.
- If the callable object is too large for the specified buffer size, a `static_assert` will be triggered at compile time.
- When called in an empty state, `ebd::fn` will throw `std::bad_function_call` (if exceptions are enabled).
- When called in an empty state, `ebd::fn` will call `std::terminate()`.

## See Also

- [`ebd::detail::function`](./detail/function.md) - The underlying implementation
- [`ebd::unique_fn`](./unique_fn.md) - For move-only callables
- [`ebd::safe_fn`](./safe_fn.md) - For exception-safe callables
- [`ebd::classic_fn`](./classic_fn.md) - For callables with `std::bad_function_call` on empty
- [`ebd::fn_ref`](./fn_ref.md) - For non-owning views of callables
6 changes: 3 additions & 3 deletions docs/api/fn_ref.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ process_data(100, &handle_result);

- `ebd::fn_ref` is a non-owning view, so the underlying callable object must outlive the view.
- `ebd::fn_ref` is trivially copyable and has minimal overhead.
- The buffer size is fixed to `detail::default_buffer_size::ref_buf`, which is sufficient to store function pointers and member pointers.
- `ebd::fn_ref` cannot be initialized with rvalue references, as it would create a dangling reference.
- The buffer size is fixed to `detail::default_buffer_size::ref_buf`, which is sufficient to store function pointers.
- `ebd::fn_ref` now can be initialized with rvalue references, although it may create a dangling reference. (same as `std::function_ref`)

## Compare `ebd::fn_ref` with `std::function_ref`

Expand All @@ -125,4 +125,4 @@ process_data(100, &handle_result);
- [`ebd::detail::function`](./detail/function.md) - The underlying implementation
- [`ebd::fn`](./fn.md) - For copyable callables
- [`ebd::unique_fn`](./unique_fn.md) - For move-only callables
- [`ebd::safe_fn`](./safe_fn.md) - For exception-safe callables
- [`ebd::classic_fn`](./classic_fn.md) - For callables with `std::bad_function_call` on empty
Loading