Repository navigation
FunctionPro v1.0.0
The first stable release of FunctionPro, a type-erased callable wrapper library for modern C++, also available under the rain:: namespace alias.
Highlights
Function<R(Args...)>— copyable,std::function-like callable wrapper.MoveOnlyFunction<R(Args...)>— move-only wrapper for callables that can't be copied (e.g. one capturing astd::unique_ptr).FunctionRef<R(Args...)>— non-owning, zero-allocation reference to an existing callable.- Small Buffer Optimization: small callables stored inline with zero heap allocation; larger callables fall back to the heap transparently.
- Strong exception guarantee on
Function's copy assignment — if the source's copy constructor throws,*thisis left completely unaffected. swap()onFunctionandMoveOnlyFunctionalways goes through the callable's real move constructor, remaining correct even for callables with an internal self-pointer into their own storage.operator bool(),== nullptr, and!= nullptrfor checking whether a callable is held or referenced.reset()onFunctionandMoveOnlyFunction.- Empty-call safety: invoking any of the three types while empty throws
std::bad_function_callrather than invoking undefined behavior. rain::namespace alias for all three types, alongside their canonicalFunctionPro::names.
Performance
The implementation is designed around avoiding allocation wherever the callable's size allows it.
- Inline (SBO) storage for callables under the size/alignment threshold, decided at compile time by
SBOTraits<T>::fits. - Heap fallback only for callables that don't fit inline, transparent to the call site either way.
FunctionRefnever allocates and never copies the referenced callable, regardless of its size.- Type-erased dispatch through a single
VTablepointer per instance, generated once per bound type byVTableFactory. swap()avoids a three-way temporary when only one side holds a callable, transferring it in a single move instead.
Benchmarks
Measured against std::function, std::move_only_function, and std::function_ref at 10K / 100K / 1M iterations. std::function_ref comparisons ran solo (BENCH_SOLO) on this toolchain, since std::function_ref is a C++26 feature not yet shipped everywhere — FunctionRef numbers below are its own timings, not a delta. Full results: benchmarks/results/v1_0_0.md.
| Operation | FunctionPro (1M) | std (1M) | Δ |
|---|---|---|---|
| Fn Move Construct (small) | 1.79 ms | 10.96 ms | +510.9% |
| Fn Move-assign | 1.50 ms | 5.04 ms | +236.7% |
| Fn Move Construct (large) | 13.41 ms | 23.71 ms | +76.9% |
| MoveOnlyFn Construct/destroy | 824.71 us | 1.36 ms | +64.8% |
| MoveOnlyFn Move-assign | 1.63 ms | 2.46 ms | +50.3% |
| Fn::operator() (empty) | 714.73 ms | 1.04 s | +45.7% |
| Fn Bind (small) | 2.08 ms | 2.91 ms | +40.1% |
| Fn Copy Construct (small) | 2.73 ms | 3.54 ms | +29.6% |
| MoveOnlyFn Move Construct (small) | 1.91 ms | 2.45 ms | +28.1% |
| Fn::reset() | 12.60 ms | 13.09 ms | +3.9% |
| Fn::operator() | 1.39 ms | 1.36 ms | -2.2% |
| Fn::operator==(nullptr) | 286.32 us | 270.72 us | -5.4% |
| Fn Construct/destroy (empty) | 874.65 us | 818.60 us | -6.4% |
| Fn Copy-assign | 5.18 ms | 3.27 ms | -36.9% |
| Fn::swap() | 16.69 ms | 2.46 ms | -85.3% |
Fn::* rows compare against std::function; MoveOnlyFn::* rows
compare against std::move_only_function.
FunctionRef has no comparison table above since std::function_ref
(C++26) isn't shipped on this toolchain yet — these are solo timings,
not deltas:
| Operation | FunctionPro (1M) |
|---|---|
| FnRef::operator() | 2.04 ms |
| FnRef::operator() (empty) | 821.38 ms |
| FnRef::operator() (function pointer) | 1.64 ms |
| FnRef Move Construct | 541.39 us |
| FnRef Bind | 548.13 us |
| FnRef Copy Construct | 270.72 us |
| FnRef Construct/destroy (empty) | 270.72 us |
| FnRef Assign | 270.72 us |
| FnRef::operator Bool() | 270.72 us |
| FnRef::operator==(nullptr) | 270.71 us |
Notably, FnRef::operator() bound to a function pointer (1.64 ms)
beats a lambda-bound call (2.04 ms) — the function-pointer fast path
(see Features) skipping the object-pointer indirection shows up
directly in the numbers.
FunctionPro's biggest wins are in moving and empty-call handling: heap-stored moves are a plain pointer transfer rather than a reallocation, and an empty call fails fast through a single vtable check rather than whatever internal branching the std counterparts use for the same case.
The trade-off is in the two operations where FunctionPro does deliberately more work for correctness. swap() always goes through the callable's real move constructor instead of swapping raw storage bytes, so it stays correct for callables with an internal self-pointer into their own storage — that guarantee costs real time next to a simpler byte-swap. Copy-assign builds the copy in a temporary before touching *this, so a throwing copy constructor leaves the destination untouched — the strong exception guarantee, paid for on every copy-assign, not just the ones that throw.
Testing
The project includes a comprehensive test suite covering:
- Unit tests
- Integration tests
- Lifecycle tests
- Move semantics
- Copy semantics
- Regression tests
- Exception safety
- SBO/heap boundary behavior
- Empty-state and moved-from behavior
- Self-referencing callable correctness under
swap() - Full construct/copy/move/invoke/destroy coverage of the type-erased dispatch layer (
VTableFactory) across every distinct bound callable type introduced by the suite - All three types (
Function,MoveOnlyFunction,FunctionRef)
Code Coverage
The current test suite achieves:
- 95.6% line coverage (152/159)
- 99.1% function coverage (349/352)
| Directory | Line Coverage | Function Coverage |
|---|---|---|
FunctionPro |
94.6% (123/130) | 98.5% (131/133) |
FunctionPro/Detail |
100.0% (29/29) | 99.5% (218/219) |
FunctionPro/Detail — CallableStorage, SBOTraits, VTable, VTableFactory — now hits essentially every line and function. Because this layer is template-heavy, each bound callable type generates its own instantiation of VTableFactory's copy, move, invoke, destroy, get, and getMoveOnly; the test suite exercises the full operation set (construct, copy, move, invoke, destroy) against each distinct callable type it introduces rather than assuming coverage of one instantiation implies coverage of another. Coverage reports exclude test infrastructure and third-party dependencies, focusing on the FunctionPro library implementation.
Continuous Integration
Automated builds and tests are configured for:
- GCC — Debug
- GCC — Release
- Clang — Debug
- Clang — Release
- MSVC — Debug
- MSVC — Release
- AppleClang — Debug
- AppleClang — Release
Release
This release represents the first stable version of FunctionPro and establishes the initial API across Function, MoveOnlyFunction, and FunctionRef, the SBO/heap storage design, testing infrastructure, and cross-platform CI pipeline.
Installation
Clone the repository and integrate FunctionPro into your C++ project using the provided CMake configuration.
See the project documentation for build instructions, API usage, and integration details.