SkillAgentSearch skills...

Callme

A header-only C++20 library of fast delegates and events

Install / Use

/learn @bitsbakery/Callme
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

CallMe - fast delegates and events

CallMe is a header-only library of fast delegates and events for C++20.

Features

  • Type-erased fast delegates. Target callables' types are erased by templated thunk functions.
  • Singlecast and multicast delegates (aka "events")
  • Supported targets are functions, static and non-static member functions, functors (including lambda functions).
  • Singlecast delegates come in two flavors, non-owning (aka function_ref/function reference/function_view/function view) and owning ones.
  • The delegates are very lightweight and have a fixed size for holding 2 or 3 pointers.
  • Non-owning delegates don't allocate memory on the heap.
  • Very low overhead compared to directly calling target callables. In many cases the overhead is zero, see benchmark.
  • Easy-to-use, high performance events with low overhead compared to directly calling non-inlined targets. Events hold their callbacks in a contiguous container with user-adjustable small buffer optimization, allowing to avoid not only memory reallocation, but the use of heap memory altogether, giving better data locality and higher cache hits. Comfortably manage event subscriptions RAII-style, unsubscribe automatically. Adjust the inline storage and run both subscription and unsubscription in O(1) time.
  • Meaningful compiler errors. The library tries hard to make most likely compile-time errors caused by invalid use of its API readable and meaningful to users, despite heavy template metaprogramming involved.
  • Minimal and compact implementation. The library provides a very thin abstraction layer over raw function pointers. A lot of source code implements zero-cost abstractions and compile-time checks, leaving minimal code executable at runtime. This makes intentions transparent for optimizers, that are often able to fully optimize away all library code at call-sites, inline the callable target, and give zero overhead compared to directly calling the inlined callable target. See benchmark.
  • Powerful type deduction. The library heavily relies on C++17 features for CTAD, and adds lots of deduction guides. All singlecast delegates can deduce proper signatures from target callables. Avoid duplicate code by leaning on type deduction.
  • Factory functions for delegates. In addition to overloaded constructors of delegates, there is a set of factory functions covering all the constructor overloads. Factories like fromFunction(...) or fromFunctor(...) narrow down the overload resolution set for a particular class of targets. The factories are syntactically cleaner and easier to use than delegate constructors, especially for new users.
  • The library heavily relies on C++20 constraints and concepts. While nothing prevents porting the library to C++17 and SFINAE, this is a non-goal.
  • Equality testing for delegates is a non-goal. Due to implementation details, there is no single universally acceptable definition of "equal" delegates. If you need it, you will have to implement equality testing for your particular case yourself.
  • Thorough tests (functional tests, tests for invalid uses of API, benchmarks) give a good base for regression testing. People who want to hack, e.g. to fine-tune performance for special cases, will have a productive start.

Known solutions/prior art

One of the early works that became widely known is Don Clugston's research on implementation of member function pointers in different C++ compilers. Based on that research, Don proposed an early implementation of what is now called "C++ fast delegates".

That triggered a couple of other articles on CodeProject.

https://www.codeproject.com/Articles/11015/The-Impossibly-Fast-C-Delegates by Sergey Ryazanov tries to improve upon Don Clugston's work.

https://www.codeproject.com/Articles/1170503/The-Impossibly-Fast-Cplusplus-Delegates-Fixed by Sergey Kryukov tries to improve upon Sergey Ryazanov's work.

Those three works served as a basis for numerous implementations of fast delegates all over the Internet over years.

Among more modern examples is a series of articles and implementation by Matthew Rodusek:

https://bitwizeshift.github.io/posts/2021/02/24/creating-a-fast-and-efficient-delegate-type-part-1/

https://bitwizeshift.github.io/posts/2021/02/26/creating-a-fast-and-efficient-delegate-type-part-2/

https://bitwizeshift.github.io/posts/2021/02/26/creating-a-fast-and-efficient-delegate-type-part-3/

https://github.com/bitwizeshift/Delegate

Matthew shows how to use C++17 and its class template argument deduction for automatically deducing the signatures of delegates.

C++ proposal P0792 is an attempt to standardize function_ref, a "type-erased callable reference". The proposal mentions the following implementations of function_ref:

By 2023, almost every large well-known C++ project, such as a GUI framework or a game engine, has its own implementation of fast delegates. There are dozens of implementations of fast delegates in circulation, most of which are proprietary.

How to use

CallMe is a header-only library.

  • To use only [singlecast] delegates: copy CallMe.h to your project directory and #include CallMe.h.

  • To use events: copy small_vector.h, CallMe.h, CallMe.Event.h to your project directory and #include CallMe.Event.h. The latter includes CallMe.h, so including CallMe.Event.h gives access to singlecast delegates and events.

The public API is in the namespace CallMe.

The following compilers have been tested and can compile and pass all CallMe tests:

  • GCC 12.2.1 x86_64 on Linux
  • Clang 15.0.7 x86_64 on Linux
  • Visual C++ 2022 Version 17.5.3, platform toolset "Visual Studio 2022 (v143)" x64 and x86 on Windows

Singlecast delegates

CallMe has extensive functional tests with many examples covering all supported overloads and features.

There are two kinds of singlecast delegates, Delegate<...> and OwningDelegate<...>.

Delegates are called using the operator() or invoke(...) member functions. Both functions are identical.

Delegate<...> does not own target callables, it references them through pointers. Delegate<...> is in the same category as function_ref from proposal p0792. When working with Delegate<...>, you have to make sure that all referenced targets are valid/alive for as long as you need to call them via Delegate<...>.

OwningDelegate<...> owns its targets, so it is always safe to call OwningDelegate<...>. The ownership is exclusive, similar to unique_ptr. That is why OwningDelegate<...> cannot be copied, it is a move-only type like unique_ptr.

Both Delegate<...> and OwningDelegate<...> can be default-constructed. Invoking default-constructed delegates has no effects.

CallMe does not have such notions as to "bind" or "rebind" a target to a delegate. A delegate is constructed with its target right away.

If you need to "rebind" a delegate to a new target, construct a new delegate from the new target and assign the new delegate to the old one.

Default-constructed delegates are used in this way too: there is a variable with a default-constructed delegate and you assign a new delegate to the variable.

Delegate<...> can be both copy-constructed/assigned and move-constructed/assigned, though in the current implementation there is no difference.

OwningDelegate<...> can be move-constructed/assigned.

Functors

Delegates for functors are constructed as follows:

int a = 1;
auto lambda = [a](int i) mutable{    
    return i + a;
};
        
Delegate<int(int)> delegate1(&lambda);
Delegate delegate2(&lambda);
auto delegate3 = fromFunctor(&lambda);

delegate1 has an explicitly declared signature, delegate2 and delegate3 have the same signature deduced. These three basic ways of constructing a delegate are available for both Delegate<...> and OwningDelegate<...> and all kinds of targets.

Notice that lambda is a variable instantiated before delegates. Delegate<...> does not own targets, it can only reference existing targets, and you have to make sure that the targets are alive for as long as you invoke them via Delegate<...> (if that is unacceptable for your scenario, use `OwningDele

View on GitHub
GitHub Stars29
CategoryDevelopment
Updated1mo ago
Forks4

Languages

C++

Security Score

95/100

Audited on Feb 9, 2026

No findings