FunctionSpeaker is a lightweight, header-centric modern C++20 library providing a flexible, memory-safe, and type-safe implementation of the multicast delegate / observer pattern. It enables decoupled, event-driven architectures for games, applications, and embedded systems.
- Type-Safe Multicast Delegates: Support for both broadcast payloads (
MultiCastDelegate<Args...>) and bound arguments (MultiCastDelegate<>or CTAD). - Universal Callable Support:
- Member functions (both non-const and
const) - Free functions and static methods
- Lambdas and functors (stateless or capturing)
- Member functions (both non-const and
- Memory Safety & Value Semantics:
- Fully compliant with the Rule of Five (no raw-pointer leaks, safe deep-copying, and
noexceptmoving). - Eliminates double-free and use-after-free pitfalls.
- Fully compliant with the Rule of Five (no raw-pointer leaks, safe deep-copying, and
- Reentrancy Safety:
- Callbacks can safely add or remove listeners during execution without iterator invalidation.
- Lifecycle Management:
- Unsubscribe by subscription handle:
delegate.Remove(handle)ordelegate -= handle. - Unsubscribe by instance pointer:
delegate.Remove(&instance)ordelegate -= &instance. - Check membership:
delegate.Contains(handle),delegate.Contains(&instance). - Inspect state:
delegate.Empty(),delegate.Size(), andif (delegate).
- Unsubscribe by subscription handle:
- Modern C++20:
- Built with
std::invoke,std::apply, and C++20 concepts/type constraints for clear compiler diagnostics. - Supports function signature syntax:
MultiCastDelegate<void(int, double)>.
- Built with
- Operator Overloads:
operator()for invocation:onEvent(arg1, arg2);operator+=to register callbacks:onEvent += []() { ... };operator-=to unregister callbacks.
FunctionSpeaker is contained within FunctionSpeaker.h. Simply include the header:
#include "FunctionSpeaker.h"Using CMake FetchContent or add_subdirectory:
add_subdirectory(path/to/FunctionSpeaker)
target_link_libraries(YourProject PRIVATE FunctionSpeaker::FunctionSpeaker)Publishers declare an event with argument types and broadcast data to all subscribers:
#include "FunctionSpeaker.h"
#include <iostream>
class Player {
public:
Player(int initialHealth) : health(initialHealth) {}
void TakeDamage(int damage) {
health = std::max(0, health - damage);
onHealthChanged(health); // Invokes all listeners with the new health
}
// Broadcast delegate taking (int currentHealth)
FunctionSpeaker::MultiCastDelegate<int> onHealthChanged;
private:
int health;
};
class HealthUI {
public:
void UpdateHealth(int newHealth) const {
std::cout << "UI: Health is now " << newHealth << " HP\n";
}
};
int main() {
Player player(100);
HealthUI healthUI;
// Register a const member function
auto handle = player.onHealthChanged.Add(&healthUI, &HealthUI::UpdateHealth);
// Register a lambda
player.onHealthChanged.Add([](int hp) {
std::cout << "Audio: Played heartbeat sound for HP: " << hp << '\n';
});
// Trigger events
player.TakeDamage(30);
// Unsubscribe when done
player.onHealthChanged.Remove(handle);
player.TakeDamage(20);
return 0;
}Callbacks can have their arguments bound at registration time:
#include "FunctionSpeaker.h"
#include <iostream>
class AudioSystem {
public:
void PlaySound(const std::string& soundName, float volume) {
std::cout << "Playing " << soundName << " at volume " << volume << '\n';
}
};
int main() {
AudioSystem audio;
FunctionSpeaker::MultiCastDelegate onGameStart;
// Arguments bound at registration time
onGameStart.Add(&audio, &AudioSystem::PlaySound, "intro_fanfare.wav", 1.0f);
onGameStart.Add([]() {
std::cout << "Game started!\n";
});
// Execute all registered actions
onGameStart.ExecuteAll();
return 0;
}namespace FunctionSpeaker {
template<typename... Args>
class MultiCastDelegate;
}// Member function (supports non-const and const methods)
Handle Add(T* instance, MemberFunc func, BoundArgs&&... bound);
// Free function, lambda, or functor
Handle Add(Callable&& callable, BoundArgs&&... bound);
// Callable tied to an instance pointer for lifecycle tracking
Handle AddWithContext(const T* instance, Callable&& callable, BoundArgs&&... bound);
// Operator += shortcut
Handle operator+=(Callable&& callable);// Remove by handle
bool Remove(Handle handle);
bool operator-=(Handle handle);
// Remove all callbacks associated with an instance pointer
std::size_t Remove(const void* instance);
std::size_t operator-=(const void* instance);
// Remove all callbacks
void Clear() noexcept;// Execute all registered callbacks
void ExecuteAll(Args... args) const;
// Call operator overload
void operator()(Args... args) const;bool Empty() const noexcept;
std::size_t Size() const noexcept;
bool Contains(Handle handle) const noexcept;
bool Contains(const void* instance) const noexcept;
explicit operator bool() const noexcept;# Configure with tests and examples enabled
cmake -B build
# Build Release binaries
cmake --build build --config Release
# Run automated tests
ctest --test-dir build -C Release --output-on-failure
# Run the example
./build/Release/FunctionSpeakerExampleThis project is licensed under the MIT License - see the LICENSE.txt file for details.