diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 92fd066..c792dfe 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -14,6 +14,7 @@ on: - 'include/**' - 'benchmark/**' - '.github/workflows/benchmark.yml' + workflow_dispatch: jobs: # Ubuntu check diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7d7827a..9316004 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -6,6 +6,7 @@ on: pull_request: branches: [ main, version-* ] types: [ opened, synchronize, reopened ] + workflow_dispatch: jobs: # GCC 13 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51bf941..e76fa64 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/README.md b/README.md index 9331e60..62127bb 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@

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

@@ -32,7 +32,7 @@ template template class unique_fn; // Wrapper for movable, especially move-only callable objects. template - 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 fn_ref; // View (non-owning wrapper) for callable objects. } @@ -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`*. @@ -126,7 +126,7 @@ 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 @@ -134,19 +134,19 @@ auto main() -> int { | 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`). @@ -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 @@ -202,7 +202,7 @@ auto f = ebd::make_fn(Ambiguous_Callable_Object); // The Callable_Object should be unambiguously callable (non-overload) if `Signature` is omitted. auto f = ebd::make_fn(Callable_Object); auto f = ebd::make_fn(Callable_Object); -auto f = ebd::make_fn(Callable_Object); +auto f = ebd::make_fn(Callable_Object); auto f = ebd::make_fn(Callable_Object); ``` @@ -216,7 +216,7 @@ auto f = ebd::make_fn(std::in_place_type, {/*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. @@ -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; } @@ -276,7 +276,7 @@ import ebd.function; auto main() -> int { ebd::fn fn1 = []() { /* ... */ }; ebd::unique_fn fn2 = []() { /* ... */ }; - ebd::safe_fn fn3 = []() { /* ... */ }; + ebd::classic_fn fn3 = []() { /* ... */ }; ebd::fn_ref fn4 = fn2; auto fn5 = ebd::make_fn([]() { /* ... */ }); @@ -301,11 +301,11 @@ Go to the `/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 @@ -313,7 +313,7 @@ Go to the `/test/` directory, and follow the instructions in [`test/README ### 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. diff --git a/docs/api/basic_fn.md b/docs/api/basic_fn.md index 9150da9..476da93 100644 --- a/docs/api/basic_fn.md +++ b/docs/api/basic_fn.md @@ -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. @@ -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 \ No newline at end of file diff --git a/docs/api/classic_fn.md b/docs/api/classic_fn.md new file mode 100644 index 0000000..a343416 --- /dev/null +++ b/docs/api/classic_fn.md @@ -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 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 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 diff --git a/docs/api/detail/function.md b/docs/api/detail/function.md index 4bf5667..a60cc2f 100644 --- a/docs/api/detail/function.md +++ b/docs/api/detail/function.md @@ -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`. \ No newline at end of file diff --git a/docs/api/fn.md b/docs/api/fn.md index 1a84206..519a0a5 100644 --- a/docs/api/fn.md +++ b/docs/api/fn.md @@ -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 @@ -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 \ No newline at end of file diff --git a/docs/api/fn_ref.md b/docs/api/fn_ref.md index 3f5ddc1..28837b4 100644 --- a/docs/api/fn_ref.md +++ b/docs/api/fn_ref.md @@ -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` @@ -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 diff --git a/docs/api/make_fn.md b/docs/api/make_fn.md index 32d9796..2772193 100644 --- a/docs/api/make_fn.md +++ b/docs/api/make_fn.md @@ -9,7 +9,7 @@ It can: - deduce the signature of a lambda or other uniquely callable functor, - choose `ebd::fn` or `ebd::unique_fn` automatically, - build an empty wrapper with an explicit signature, -- create a wrapper with a specific wrapper type such as `ebd::safe_fn` or `ebd::fn_ref`. +- create a wrapper with a specific wrapper type such as `ebd::classic_fn` or `ebd::fn_ref`. ## Overloads @@ -18,16 +18,12 @@ It can: ```cpp template , - std::size_t BufferSize = sizeof(Class), - typename Fn = detail::conditional_t< - std::is_copy_constructible::value, - fn, - unique_fn>, bool NoThrow = detail::is_nothrow_construct_from_functor::value> -EMBED_NODISCARD inline Fn make_fn(Functor&& functor) noexcept(NoThrow); +EMBED_NODISCARD inline fn +make_fn(Functor&& functor) noexcept(NoThrow); ``` -Creates a wrapper for a class-type callable when the signature is specified explicitly. For copyable functors, the resulting type is `ebd::fn`. +Creates an `ebd::fn` for a class-type callable when the signature is specified explicitly. The buffer size is automatically deduced as `sizeof(Class)`. ### 2. Move-only functor with explicit signature @@ -61,11 +57,21 @@ make_fn(Ret (*func_ptr)(Args...)) noexcept; Creates an `ebd::fn` from a free-function pointer and deduces both signature and buffer size. +### 4b. noexcept function pointer with deduced signature (C++17+) + +```cpp +template +EMBED_NODISCARD inline fn +make_fn(Ret (*func_ptr)(Args...) noexcept) noexcept; +``` + +Creates an `ebd::fn` from a noexcept free-function pointer. The noexcept qualifier is preserved in the signature. Only available when noexcept is part of the type system (C++17 or `__cpp_noexcept_function_type >= 201510L`). + ### 5. Function pointer with explicit signature ```cpp template ::pure_sig*> + typename FunctionPtr = typename detail::unwrap_signature::pure_sig_noex*> EMBED_NODISCARD inline fn make_fn(FunctionPtr func_ptr) noexcept; ``` @@ -118,7 +124,7 @@ template EMBED_NODISCARD inline auto make_fn(Ret(Class::* memfunc)(Args...) C V REF NOEXCEPT) noexcept -> fn< - Ret(detail::get_qualified_with_t, Args...) const, + Ret(detail::get_qualified_with_t, Args...) const NOEXCEPT, sizeof(memfunc) >; ``` @@ -143,10 +149,11 @@ Creates an `ebd::fn` from a pointer to member function using the specified signa template ::type> EMBED_NODISCARD inline auto make_fn(T Class::* ptr_memobj) noexcept --> fn; +-> fn::value), + sizeof(ptr_memobj)>; ``` -Creates an `ebd::fn` that reads a member object from an instance. +Creates an `ebd::fn` that reads a member object from an instance. The noexcept qualifier is deduced from the member access expression. ### 12. In-place construction (C++17+) @@ -174,7 +181,7 @@ template