Skip to content

v1.0.0

Latest

Choose a tag to compare

@github-actions github-actions released this 25 Jul 02:18
· 78 commits to main since this release

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 a std::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, *this is left completely unaffected.
  • swap() on Function and MoveOnlyFunction always 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 != nullptr for checking whether a callable is held or referenced.
  • reset() on Function and MoveOnlyFunction.
  • Empty-call safety: invoking any of the three types while empty throws std::bad_function_call rather than invoking undefined behavior.
  • rain:: namespace alias for all three types, alongside their canonical FunctionPro:: 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.
  • FunctionRef never allocates and never copies the referenced callable, regardless of its size.
  • Type-erased dispatch through a single VTable pointer per instance, generated once per bound type by VTableFactory.
  • 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.