etl/docs/callbacks/delegate-vs-inplace_function.md
John Wellbelove 4a88884b39
Issue/add hugo support for documentation (#1449)
* Add ranges

* Initial Hugo setup

* Work in progress

* Added selection for local or remote site

* Updated to 'light' theme

* Changed to using Hextra Hugo theme

* Changed to using Hextra Hugo theme

* Changed to Hextra Hugo theme

* Change to Hextra Hugo theme

* Updated Hugo setup.

* Updated Hugo setup.

# Conflicts:
#	docs/releases/_index.md

* Work in progress

* Added new fonts

Added new documentation

* Latest documentation updates

* Latest documentation updates

# Conflicts:
#	docs/containers/array.md
#	docs/containers/array_view.md
#	docs/containers/array_wrapper.md
#	docs/containers/bip_buffer_spsc_atomic.md
#	docs/containers/bitset.md
#	docs/containers/indirect_vector.md
#	docs/containers/vector.md
#	docs/getting-started/compilers.md

* Added bloom_filter markdown doc

* Added more documentation

Updated CSS for light and dark modes

* Fixed some menus

Added mode documentation files

* Updated CSS rules

Added badges to home page
Added uniqur_ptr + pool tutorial

* Fixed formatting on the home page markdown

Modified light amd dark code formatting

* Updated unique_ptr-with-pool

* Added container and shared message tutorials

* Updates to documentation

* Added const_multimap

* Updated source-formatting.md

* Added initial raw text files form Web site editor

* Innore coverage build directory

* Exported raw text documentation files from the web site editor

* Hugo updates

* Added Hugo intalation and markdown descriptions

* More addition to the documentation

* Added closure.md and updates to delegate.md

* Added format.md

* Added documentation for etl::delegate_observable, etl::function, Base64 codec

* Added io_port documentation

* Added basic_format_spec

* Added documentation for string_stream and string utilities.

* Added more documentation

Updated the documentation CSS

* Added documentation for clocks, day, duration

* Added more documentation for chrono classes

Updated callouts

* More chrono documentation

* Completed chrono documentation

* Maths functions documentation

* Completed maths documentation

* Completed maths documentation

* Completed maths documentation

* Completed maths documentation

* Added multiple documentation files

* Added iterator.md

* Added debug_count.md and versions.md

* Added debug_count.md and versions.md

* Added more documentation

* More documentation

* Added some design pattern documentation

Modified some of the layout files
Modified the About documentation

* Converted more documentation pages

Modified the site CSS

* Added more documentation

Moced some documentation files to new directories

* Added more documentation

Tweaks to CSS

* Added callback_timer_deferred_locked documentation

* Added callback_timer_locked documentation

* More documentation updates

* More documentation updates

* More documentation updates

* New documentation files.

Harmonised file name format

* New documentation files.

* Multiple document updates

* Multiple document updates

* Final conversion of web pages

* Updates before PR

* Updates before PR

* Updates before PR

# Conflicts:
#	docs/blog/_index.md

* Final pre PR updates

* Updates to message framework documentation

* Renamed directory

* Fix spelling

* Added author and date to blog files

Moved documentation files merged from development

* Fixed 'Description' typo

* Fix typos

# Conflicts:
#	docs/IO/io_port.md
#	docs/containers/sets/const-multiset.md
#	docs/containers/sets/const-set.md
#	docs/maths/correlation.md
#	docs/maths/gamma.md

* Renamed two files to lower case

* Minor renaming

* Added author and date

* Updated callout on bresenham_line.md

Added support for showing the ETL version on the documentation first page, by copying the version.txt file as a hugo asset.
Updated the Python 'update_release.py' to copy 'version.txt'

* Replace space in filename with hyphen.

Added more information to hugo-commands.md

* Replace space in filename with hyphen.

Added more information to hugo-commands.md

# Conflicts:
#	docs/getting-started/view-the-docs-locally/hugo-commands.md

* Added a link to pseudo_moving_average.md

* Updated title pages for groups

* Fixed missing 404 for non-existent pages

* Fixed coordinate variable names in the 'Calculating the intersection' example

---------

Co-authored-by: Roland Reichwein <Roland.Reichwein@bmw.de>
Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.com>
Co-authored-by: John Wellbelove <john.wellbelove@etlcpp.co.uk>
2026-06-06 13:12:44 +01:00

9.6 KiB
Raw Permalink Blame History

title weight
delegate vs inplace_function 1

Efficiency and performance

Invocation cost

delegate
One indirect call via stub function pointer; no allocation; forwards args directly; minimal overhead.

inplace_function
One indirect call via vtable invoke pointer; no allocation; overhead effectively the same as delegate.

Copy / move cost

delegate
Trivial copy/move (object pointer + stub pointer).

inplace_function
Copies/moves the stored callable in an inline buffer; cost depends on callable traits; may run destructor on clear/reset.

Size / footprint

delegate
Typically two pointers; very small, stable footprint.

inplace_function
Buffer + vtable pointer + object pointer; size depends on Object_Size/Object_Alignment.

Ownership / lifetime

delegate
Non-owning for functors/lambdas; stores pointer to external callable; caller must ensure lifetime.

inplace_function
Owning; stores a copy/move of the callable inline (RAII).

When to prefer

delegate
Minimal footprint, cheapest copies, free/member function binding, controlled lifetimes.

inplace_function
Value semantics, safe storage of lambdas with captures, uniform SBO wrapper.

External API differences

Template shape and size

delegate<TSignature>
inplace_function<TSignature, Object_Size = 32, Object_Alignment = alignof(void*)>

Construction / binding

delegate
create<FreeFn>()
create(T& obj, Method) and create(const T& obj, ConstMethod) at run time
create<T, Method, Instance>()
Construct from functor/lambda by reference (non-owning); rvalues deleted to avoid dangling
set() mirrors create() for re-binding

inplace_function
Constructor from function pointer
Constructor from object + (const/non-const) member function (run time)
Constructor from functor/lambda by (const) reference; callable stored inline (owning)
set() mirrors constructors; also set<FreeFn>() and set<T, Method, Instance>() (compile time)
create<FreeFn>(), create<T, Method, Instance>() (compile time)
make_inplace_function helpers: make_inplace_function(free_fn), make_inplace_function(obj, &T::Method) , make_inplace_function<TSignature>(lambda).

Equality and swap

delegate
operator==/!= compare stub and object; structural equality.

inplace_function
operator==/!= with nullptr; swap() provided; no general equality between two functions.

Introspection helpers

delegate
delegate_tag
is_delegate<T>.

inplace_function
is_inplace_function<T>.

Call helpers

is_valid()
explicit bool
call_if(...)
call_or(alternative, ...)
call_or<FreeFn>(...).

Functor storage semantics

delegate
Non-owning pointer to external functor/lambda.

inplace_function
Owns a copy/move in fixed buffer.

Size control

delegate
Fixed small size (two pointers).

inplace_function
User-controlled via Object_Size/Object_Alignment; aliases inplace_function_for and inplace_function_for_any to compute required size/alignment.

C++ language features

Classic non-type template parameter APIs; C++17 "auto" make_* helpers exist when enabled.

Performance differences:

Call overhead

delegate
One indirect call through a stub function pointer; minimal overhead.

inplace_function
One indirect call via vtable->invoke(object, ...); typically one extra pointer load; practically similar.

Copy / move and lifetime

delegate
Trivial POD-like copy/move (object pointer + stub); no construction/destruction of target.

inplace_function
Copies/moves/destroys the stored callable in the inline buffer; cost depends on callable traits.

Construction / binding cost

delegate
Binding free/member functions sets two pointers; binding a functor stores its address (non-owning).

inplace_function
Constructs a copy/move of the callable into the SBO buffer; more work up front.

Memory footprint and cache locality

delegate
~2 pointers regardless of target; high container density and good cache locality.

inplace_function
Buffer + vtable pointer + object pointer; larger, size depends on Object_Size/Object_Alignment.

Clear / reset cost

delegate
Clears pointers; no destructor invocation.

inplace_function
May run callables destructor; potentially non-trivial.

Determinism / Real-time suitability

delegate
Very predictable and minimal costs at call/copy/reset; good for ISR/RT paths.

inplace_function
Deterministic at call; copy/move/reset depend on callables traits (still allocation-free).

Rule of thumb

  • Use delegate when:
    You only bind free/member functions or non-owning functors and want the smallest handle with the cheapest copies.

  • Use inplace_function when:
    You must own captured state safely (value semantics) and still avoid the heap; accept slightly larger handle/copy costs for safety.

Choose etl::delegate when

  • You need the smallest possible callable handle (two pointers) for large arrays/vectors of callbacks.
  • You must avoid any allocation, construction, destruction, or SBO buffer management in the callable wrapper.
  • You bind free functions or member functions and the target objects lifetime is externally guaranteed.
  • You want trivial copy/move/equality semantics (copy two pointers; compare stub+object) for fast shuffling/lookup.
  • You need deterministic, RT/ISR-safe behavior with no hidden destructors or move/destruct paths on clear/reset.
  • You build static/ROM lookup tables of callbacks (compile-time create(), create<T,Method,Instance>()).
  • You store callbacks in fixed-capacity containers (etl::array/vector) and must minimize memory per element.
  • You need ABI-stable, non-owning callback tokens passed across modules without copying callables.
  • You already use etl::delegate_service or similar indexed dispatch; its API expects etl::delegate.
  • You don't want to size/tune an inline buffer (Object_Size/Object_Alignment) per signature or platform.

Typical scenarios

  • Interrupt vectors, GPIO/event ISR callbacks, hard real-time control loops.
  • Command/ID -> handler tables, message routing switchboards, state-machine transition actions.
  • Global registries/singletons where handler objects are static or live for program lifetime.
  • Shared tables of callbacks across DLLs/modules where owning captured state is undesirable.

Why etl::delegate over etl::inplace_function in these cases

  • Lower footprint: always two pointers versus user-sized SBO buffer + vtable pointer + object pointer.
  • Lower copy/move cost: trivial POD-like copies; no callable copy/move/dtor required.
  • Non-owning by design: avoids accidental copies of heavy callables; no lifetime surprises at clear/reset.
  • Compile-time binding support: completely payload-free delegates for fixed targets (no storage touched).
  • Equality: pointer-based equality lets you deduplicate/lookup handlers efficiently.

Notes

  • etl::delegate is non-owning; only choose it when the bound object outlives the delegate (rvalue lambdas are rejected by API).
  • Prefer etl::inplace_function0 when you must own captured state (value semantics) or need to accept arbitrary lambdas safely.

Choose etl::inplace_function when

  • You need value semantics (own the callable) so lifetimes are safe without tracking external objects.
  • You want to accept arbitrary lambdas with captures and store them inline using small-buffer optimization (no heap).
  • You need a single uniform callable wrapper that erases the concrete type across modules/APIs.
  • You plan to copy/move callbacks between threads/queues safely (callable is self-contained in the buffer).
  • You must avoid dynamic allocation but still need to store captured state (tune Object_Size/Object_Alignment).
  • You want compile-time or run-time binding to free functions, member functions, and functors in one type.
  • You need reset/clear semantics that correctly destroy the stored callable (RAII).
  • You prefer exceptions on misuse (invoking uninitialized) to surface bugs early.

Typical scenarios

  • Job systems, task queues, and executors that capture small state per task without heap churn.
  • Pipelines and event buses that accept user-provided lambdas with captures.
  • Configurable command tables where handlers carry lightweight configuration/state.
  • Deferred work items, timeouts, and continuations stored in fixed-capacity containers.
  • API boundaries returning/storing callbacks where the provider shouldn't manage the callee's lifetime.

Why etl::inplace_function over etl::delegate in these cases

  • Ownership: inplace_function owns the callable; no external lifetime management needed.
  • Safety with captures: stores copies/moves of lambdas/functors; no dangling pointers to external objects.
  • Uniform wrapper: a single type covers free/member/functor targets with the same call site and storage.
  • No heap: SBO avoids allocations while still allowing captured state; predictable embedded-friendly footprint.
  • Destruction: clear()/reset() run the callable's destructor when needed; no leaks or latent state.

Notes

  • Tune the buffer size/alignment to the largest expected callable; too-small buffers will static_assert.
  • Copy/move cost depends on the callable; prefer lightweight captures for best performance.
  • Invocation overhead is one indirect call through a vtable stub (similar to delegate).
  • Use make_inplace_function helpers or create<> to bind targets; is_valid(), call_if(), and call_or() are available.
  • If you only bind free/member functions and can guarantee lifetimes, etl::delegate is smaller and cheaper to copy.