[core] Replace std::function with lightweight Callback in CallbackManager

Replace std::function with a lightweight type-erased Callback struct
(8 bytes on 32-bit vs 16 for std::function) in CallbackManager and
LazyCallbackManager. Using C++20 if constexpr, small trivially-copyable
callables like [this] lambdas (81% of all callback registrations) are
stored inline in the function pointer context without heap allocation.

This eliminates std::function null checks (and __throw_bad_function_call)
from all entity callback paths, and templatizes add_on_*_callback methods
across all entity types so lambdas flow through without being wrapped in
std::function first.

Also converts EntityStateBase from hand-rolled nullable CallbackManager
pointers to LazyCallbackManager for consistency.

Measured flash savings:
- ESP8266 simple:        -160 B
- ESP8266 ratgdo:        -592 B
- ESP32 IDF (large):     -456 B
- ESP32 IDF (minimal):   -212 B
This commit is contained in:
J. Nick Koston
2026-03-15 22:20:42 -10:00
parent 1377776d21
commit bfc5a210bc
38 changed files with 147 additions and 136 deletions
@@ -51,22 +51,6 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
}
}
void AlarmControlPanel::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_cleared_callback(std::function<void()> &&callback) {
this->cleared_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_chime_callback(std::function<void()> &&callback) {
this->chime_callback_.add(std::move(callback));
}
void AlarmControlPanel::add_on_ready_callback(std::function<void()> &&callback) {
this->ready_callback_.add(std::move(callback));
}
void AlarmControlPanel::arm_with_code_(AlarmControlPanelCall &(AlarmControlPanelCall::*arm_method)(),
const char *code) {
auto call = this->make_call();
@@ -37,25 +37,24 @@ class AlarmControlPanel : public EntityBase {
*
* @param callback The callback function
*/
void add_on_state_callback(std::function<void()> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
/** Add a callback for when the state of the alarm_control_panel clears from triggered
*
* @param callback The callback function
*/
void add_on_cleared_callback(std::function<void()> &&callback);
/** Add a callback for when the state of the alarm_control_panel clears from triggered. */
template<typename F> void add_on_cleared_callback(F &&callback) {
this->cleared_callback_.add(std::forward<F>(callback));
}
/** Add a callback for when a chime zone goes from closed to open
*
* @param callback The callback function
*/
void add_on_chime_callback(std::function<void()> &&callback);
/** Add a callback for when a chime zone goes from closed to open. */
template<typename F> void add_on_chime_callback(F &&callback) {
this->chime_callback_.add(std::forward<F>(callback));
}
/** Add a callback for when a ready state changes
*
* @param callback The callback function
*/
void add_on_ready_callback(std::function<void()> &&callback);
/** Add a callback for when a ready state changes. */
template<typename F> void add_on_ready_callback(F &&callback) {
this->ready_callback_.add(std::forward<F>(callback));
}
/** A numeric representation of the supported features as per HomeAssistant
*
-1
View File
@@ -20,6 +20,5 @@ void Button::press() {
this->press_action();
this->press_callback_.call();
}
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
} // namespace esphome::button
+3 -1
View File
@@ -34,7 +34,9 @@ class Button : public EntityBase {
*
* @param callback The void() callback.
*/
void add_on_press_callback(std::function<void()> &&callback);
template<typename F> void add_on_press_callback(F &&callback) {
this->press_callback_.add(std::forward<F>(callback));
}
protected:
/** You should implement this virtual method if you want to create your own button.
-8
View File
@@ -356,14 +356,6 @@ ClimateCall &ClimateCall::set_swing_mode(optional<ClimateSwingMode> swing_mode)
return *this;
}
void Climate::add_on_state_callback(std::function<void(Climate &)> &&callback) {
this->state_callback_.add(std::move(callback));
}
void Climate::add_on_control_callback(std::function<void(ClimateCall &)> &&callback) {
this->control_callback_.add(std::move(callback));
}
// Random 32bit value; If this changes existing restore preferences are invalidated
static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL;
+6 -2
View File
@@ -192,7 +192,9 @@ class Climate : public EntityBase {
*
* @param callback The callback to call.
*/
void add_on_state_callback(std::function<void(Climate &)> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
/**
* Add a callback for the climate device configuration; each time the configuration parameters of a climate device
@@ -200,7 +202,9 @@ class Climate : public EntityBase {
*
* @param callback The callback to call.
*/
void add_on_control_callback(std::function<void(ClimateCall &)> &&callback);
template<typename F> void add_on_control_callback(F &&callback) {
this->control_callback_.add(std::forward<F>(callback));
}
/** Make a climate device control call, this is used to control the climate device, see the ClimateCall description
* for more info.
-1
View File
@@ -139,7 +139,6 @@ bool CoverCall::get_stop() const { return this->stop_; }
CoverCall Cover::make_call() { return {this}; }
void Cover::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
void Cover::publish_state(bool save) {
this->position = clamp(this->position, 0.0f, 1.0f);
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
+1 -1
View File
@@ -125,7 +125,7 @@ class Cover : public EntityBase {
/// Construct a new cover call used to control the cover.
CoverCall make_call();
void add_on_state_callback(std::function<void()> &&f);
template<typename F> void add_on_state_callback(F &&f) { this->state_callback_.add(std::forward<F>(f)); }
/** Publish the current state of the cover.
*
+3 -1
View File
@@ -14,7 +14,9 @@ class DateTimeBase : public EntityBase {
public:
virtual ESPTime state_as_esptime() const = 0;
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
#ifdef USE_TIME
void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; }
@@ -48,8 +48,8 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase {
bool should_start() const { return this->should_start_; }
#ifdef USE_ESP32_IMPROV_STATE_CALLBACK
void add_on_state_callback(std::function<void(improv::State, improv::Error)> &&callback) {
this->state_callback_.add(std::move(callback));
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
#endif
#ifdef USE_BINARY_SENSOR
-4
View File
@@ -45,9 +45,5 @@ void Event::set_event_types(const std::vector<const char *> &event_types) {
this->last_event_type_ = nullptr; // Reset when types change
}
void Event::add_on_event_callback(std::function<void(StringRef event_type)> &&callback) {
this->event_callback_.add(std::move(callback));
}
} // namespace event
} // namespace esphome
+3 -1
View File
@@ -66,7 +66,9 @@ class Event : public EntityBase {
/// Check if an event has been triggered.
bool has_event() const { return this->last_event_type_ != nullptr; }
void add_on_event_callback(std::function<void(StringRef event_type)> &&callback);
template<typename F> void add_on_event_callback(F &&callback) {
this->event_callback_.add(std::forward<F>(callback));
}
protected:
LazyCallbackManager<void(StringRef event_type)> event_callback_;
@@ -17,8 +17,8 @@ class FactoryResetComponent : public Component {
void dump_config() override;
void setup() override;
void add_increment_callback(std::function<void(uint8_t, uint8_t)> &&callback) {
this->increment_callback_.add(std::move(callback));
template<typename F> void add_increment_callback(F &&callback) {
this->increment_callback_.add(std::forward<F>(callback));
}
protected:
-1
View File
@@ -193,7 +193,6 @@ void Fan::apply_preset_mode_(const FanCall &call) {
}
}
void Fan::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
void Fan::publish_state() {
auto traits = this->get_traits();
+3 -1
View File
@@ -122,7 +122,9 @@ class Fan : public EntityBase {
FanCall make_call();
/// Register a callback that will be called each time the state changes.
void add_on_state_callback(std::function<void()> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
void publish_state();
-2
View File
@@ -48,8 +48,6 @@ void Lock::publish_state(LockState state) {
#endif
}
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
void LockCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
this->validate_();
+3 -1
View File
@@ -150,7 +150,9 @@ class Lock : public EntityBase {
*
* @param callback The void(bool) callback.
*/
void add_on_state_callback(std::function<void()> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
protected:
friend LockCall;
@@ -198,10 +198,6 @@ MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) {
return *this;
}
void MediaPlayer::add_on_state_callback(std::function<void()> &&callback) {
this->state_callback_.add(std::move(callback));
}
void MediaPlayer::publish_state() {
this->state_callback_.call();
#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY)
@@ -155,7 +155,9 @@ class MediaPlayer : public EntityBase {
void publish_state();
void add_on_state_callback(std::function<void()> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
virtual bool is_muted() const { return false; }
-4
View File
@@ -29,8 +29,4 @@ void Number::publish_state(float state) {
#endif
}
void Number::add_on_state_callback(std::function<void(float)> &&callback) {
this->state_callback_.add(std::move(callback));
}
} // namespace esphome::number
+3 -1
View File
@@ -34,7 +34,9 @@ class Number : public EntityBase {
NumberCall make_call() { return NumberCall(this); }
void add_on_state_callback(std::function<void(float)> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
NumberTraits traits;
+2 -2
View File
@@ -34,8 +34,8 @@ class SafeModeComponent final : public Component {
void mark_successful();
#ifdef USE_SAFE_MODE_CALLBACK
void add_on_safe_mode_callback(std::function<void()> &&callback) {
this->safe_mode_callback_.add(std::move(callback));
template<typename F> void add_on_safe_mode_callback(F &&callback) {
this->safe_mode_callback_.add(std::forward<F>(callback));
}
#endif
-4
View File
@@ -42,10 +42,6 @@ StringRef Select::current_option() const {
return this->has_state() ? StringRef(this->option_at(this->active_index_)) : StringRef();
}
void Select::add_on_state_callback(std::function<void(size_t)> &&callback) {
this->state_callback_.add(std::move(callback));
}
bool Select::has_option(const std::string &option) const { return this->index_of(option.c_str()).has_value(); }
bool Select::has_option(const char *option) const { return this->index_of(option).has_value(); }
+3 -1
View File
@@ -76,7 +76,9 @@ class Select : public EntityBase {
/// Return the option value at the provided index offset (as const char* from flash).
const char *option_at(size_t index) const;
void add_on_state_callback(std::function<void(size_t)> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
protected:
friend class SelectCall;
-5
View File
@@ -79,11 +79,6 @@ void Sensor::publish_state(float state) {
#endif
}
void Sensor::add_on_state_callback(std::function<void(float)> &&callback) { this->callback_.add(std::move(callback)); }
void Sensor::add_on_raw_state_callback(std::function<void(float)> &&callback) {
this->raw_callback_.add(std::move(callback));
}
#ifdef USE_SENSOR_FILTER
void Sensor::add_filter(Filter *filter) {
// inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of
+4 -2
View File
@@ -111,9 +111,11 @@ class Sensor : public EntityBase {
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Add a callback that will be called every time a filtered value arrives.
void add_on_state_callback(std::function<void(float)> &&callback);
template<typename F> void add_on_state_callback(F &&callback) { this->callback_.add(std::forward<F>(callback)); }
/// Add a callback that will be called every time the sensor sends a raw value.
void add_on_raw_state_callback(std::function<void(float)> &&callback);
template<typename F> void add_on_raw_state_callback(F &&callback) {
this->raw_callback_.add(std::forward<F>(callback));
}
/** This member variable stores the last state that has passed through all filters.
*
-3
View File
@@ -69,9 +69,6 @@ void Switch::publish_state(bool state) {
}
bool Switch::assumed_state() { return false; }
void Switch::add_on_state_callback(std::function<void(bool)> &&callback) {
this->state_callback_.add(std::move(callback));
}
void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; }
bool Switch::is_inverted() const { return this->inverted_; }
+3 -1
View File
@@ -93,7 +93,9 @@ class Switch : public EntityBase {
*
* @param callback The void(bool) callback.
*/
void add_on_state_callback(std::function<void(bool)> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
/** Returns the initial state of the switch, as persisted previously,
or empty if never persisted.
-4
View File
@@ -29,8 +29,4 @@ void Text::publish_state(const char *state, size_t len) {
#endif
}
void Text::add_on_state_callback(std::function<void(const std::string &)> &&callback) {
this->state_callback_.add(std::move(callback));
}
} // namespace esphome::text
+3 -1
View File
@@ -30,7 +30,9 @@ class Text : public EntityBase {
/// Instantiate a TextCall object to modify this text component's state.
TextCall make_call() { return TextCall(this); }
void add_on_state_callback(std::function<void(const std::string &)> &&callback);
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
protected:
friend class TextCall;
@@ -83,13 +83,6 @@ void TextSensor::clear_filters() {
}
#endif // USE_TEXT_SENSOR_FILTER
void TextSensor::add_on_state_callback(std::function<void(const std::string &)> callback) {
this->callback_.add(std::move(callback));
}
void TextSensor::add_on_raw_state_callback(std::function<void(const std::string &)> callback) {
this->raw_callback_.add(std::move(callback));
}
const std::string &TextSensor::get_state() const { return this->state; }
const std::string &TextSensor::get_raw_state() const {
#ifdef USE_TEXT_SENSOR_FILTER
+4 -2
View File
@@ -62,9 +62,11 @@ class TextSensor : public EntityBase {
void clear_filters();
#endif
void add_on_state_callback(std::function<void(const std::string &)> callback);
template<typename F> void add_on_state_callback(F &&callback) { this->callback_.add(std::forward<F>(callback)); }
/// Add a callback that will be called every time the sensor sends a raw value.
void add_on_raw_state_callback(std::function<void(const std::string &)> callback);
template<typename F> void add_on_raw_state_callback(F &&callback) {
this->raw_callback_.add(std::forward<F>(callback));
}
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
+3 -3
View File
@@ -55,9 +55,9 @@ class RealTimeClock : public PollingComponent {
/// Get the current time as the UTC epoch since January 1st 1970.
time_t timestamp_now() { return ::time(nullptr); }
void add_on_time_sync_callback(std::function<void()> &&callback) {
this->time_sync_callback_.add(std::move(callback));
};
template<typename F> void add_on_time_sync_callback(F &&callback) {
this->time_sync_callback_.add(std::forward<F>(callback));
}
void dump_config() override;
+3 -1
View File
@@ -40,7 +40,9 @@ class UpdateEntity : public EntityBase {
const UpdateInfo &update_info = update_info_;
const UpdateState &state = state_;
void add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
}
Trigger<const UpdateInfo &> *get_update_available_trigger() {
if (!update_available_trigger_) {
update_available_trigger_ = std::make_unique<Trigger<const UpdateInfo &>>();
-1
View File
@@ -125,7 +125,6 @@ bool ValveCall::get_stop() const { return this->stop_; }
ValveCall Valve::make_call() { return {this}; }
void Valve::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
void Valve::publish_state(bool save) {
this->position = clamp(this->position, 0.0f, 1.0f);
+1 -1
View File
@@ -117,7 +117,7 @@ class Valve : public EntityBase {
/// Construct a new valve call used to control the valve.
ValveCall make_call();
void add_on_state_callback(std::function<void()> &&f);
template<typename F> void add_on_state_callback(F &&f) { this->state_callback_.add(std::forward<F>(f)); }
/** Publish the current state of the valve.
*
+9 -15
View File
@@ -299,15 +299,11 @@ template<typename T> class StatefulEntityBase : public EntityBase {
virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); }
void invalidate_state() { this->set_new_state({}); }
void add_full_state_callback(std::function<void(optional<T> previous, optional<T> current)> &&callback) {
if (this->full_state_callbacks_ == nullptr)
this->full_state_callbacks_ = new CallbackManager<void(optional<T> previous, optional<T> current)>(); // NOLINT
this->full_state_callbacks_->add(std::move(callback));
template<typename F> void add_full_state_callback(F &&callback) {
this->full_state_callbacks_.add(std::forward<F>(callback));
}
void add_on_state_callback(std::function<void(T)> &&callback) {
if (this->state_callbacks_ == nullptr)
this->state_callbacks_ = new CallbackManager<void(T)>(); // NOLINT
this->state_callbacks_->add(std::move(callback));
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callbacks_.add(std::forward<F>(callback));
}
void set_trigger_on_initial_state(bool trigger_on_initial_state) {
@@ -325,21 +321,19 @@ template<typename T> class StatefulEntityBase : public EntityBase {
virtual bool set_new_state(const optional<T> &new_state) {
if (this->state_ != new_state) {
// call the full state callbacks with the previous and new state
if (this->full_state_callbacks_ != nullptr)
this->full_state_callbacks_->call(this->state_, new_state);
this->full_state_callbacks_.call(this->state_, new_state);
// trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or
// the previous state was valid
auto had_state = this->has_state();
this->state_ = new_state;
if (this->state_callbacks_ != nullptr && new_state.has_value() && (this->trigger_on_initial_state_ || had_state))
this->state_callbacks_->call(new_state.value());
if (new_state.has_value() && (this->trigger_on_initial_state_ || had_state))
this->state_callbacks_.call(new_state.value());
return true;
}
return false;
}
bool trigger_on_initial_state_{true};
// callbacks with full state and previous state
CallbackManager<void(optional<T> previous, optional<T> current)> *full_state_callbacks_{};
CallbackManager<void(T)> *state_callbacks_{};
LazyCallbackManager<void(optional<T> previous, optional<T> current)> full_state_callbacks_;
LazyCallbackManager<void(T)> state_callbacks_;
};
} // namespace esphome
+65 -12
View File
@@ -1729,6 +1729,53 @@ constexpr float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1.
/// @name Utilities
/// @{
/// Lightweight type-erased callback (8 bytes on 32-bit) that avoids std::function overhead.
/// No null check, no exceptions, no heap allocation for small trivially-copyable callables.
///
/// With C++20 if constexpr, automatically detects [this] lambdas (sizeof <= sizeof(void*),
/// trivially copyable) and stores them inline. Larger callables are heap-allocated.
template<typename... X> struct Callback;
template<typename... Ts> struct Callback<void(Ts...)> {
void (*fn)(void *, Ts...);
void *ctx;
void call(Ts... args) const { this->fn(this->ctx, args...); }
/// Create from any callable. Small trivially-copyable callables (like [this] lambdas)
/// are stored inline in the ctx pointer without heap allocation.
template<typename F> static Callback create(F &&callable) {
using DecayF = std::decay_t<F>;
if constexpr (sizeof(DecayF) <= sizeof(void *) && std::is_trivially_copyable_v<DecayF>) {
// Small trivial callable (e.g. [this]() { this->method(); }) - store inline in ctx
Callback cb;
cb.ctx = nullptr;
// Store the callable in the ctx pointer itself (type-punning via char* is allowed)
char *dst = reinterpret_cast<char *>(&cb.ctx);
const char *src = reinterpret_cast<const char *>(&callable);
for (size_t i = 0; i < sizeof(DecayF); ++i)
dst[i] = src[i];
cb.fn = [](void *c, Ts... args) {
// Recover the callable from the ctx pointer.
// Safe under C++20 (P0593R6): byte copy into aligned storage implicitly
// creates objects of implicit-lifetime types (trivially copyable qualifies).
alignas(DecayF) char buf[sizeof(DecayF)];
const char *csrc = reinterpret_cast<const char *>(&c);
for (size_t i = 0; i < sizeof(DecayF); ++i)
buf[i] = csrc[i];
reinterpret_cast<DecayF *>(buf)->operator()(args...);
};
return cb;
} else {
// Large or non-trivial callable - heap allocate.
// Intentionally never freed: callbacks in ESPHome are registered during setup()
// and live for device lifetime. Same lifetime as the previous std::function approach.
auto *stored = new DecayF(std::forward<F>(callable));
return {[](void *c, Ts... args) { (*static_cast<DecayF *>(c))(args...); }, static_cast<void *>(stored)};
}
}
};
template<typename... X> class CallbackManager;
/** Helper class to allow having multiple subscribers to a callback.
@@ -1737,13 +1784,14 @@ template<typename... X> class CallbackManager;
*/
template<typename... Ts> class CallbackManager<void(Ts...)> {
public:
/// Add a callback to the list.
void add(std::function<void(Ts...)> &&callback) { this->callbacks_.push_back(std::move(callback)); }
/// Add any callable. Small trivially-copyable callables (like [this] lambdas)
/// are stored inline without heap allocation or std::function.
template<typename F> void add(F &&callback) { this->add_(Callback<void(Ts...)>::create(std::forward<F>(callback))); }
/// Call all callbacks in this manager.
/// Call all callbacks in this manager. No null check on invoke.
void call(Ts... args) {
for (auto &cb : this->callbacks_)
cb(args...);
cb.call(args...);
}
size_t size() const { return this->callbacks_.size(); }
@@ -1751,7 +1799,10 @@ template<typename... Ts> class CallbackManager<void(Ts...)> {
void operator()(Ts... args) { call(args...); }
protected:
std::vector<std::function<void(Ts...)>> callbacks_;
template<typename...> friend class LazyCallbackManager;
/// Non-template core to avoid code duplication per lambda type.
void add_(Callback<void(Ts...)> cb) { this->callbacks_.push_back(cb); }
std::vector<Callback<void(Ts...)>> callbacks_;
};
template<typename... X> class LazyCallbackManager;
@@ -1784,13 +1835,8 @@ template<typename... Ts> class LazyCallbackManager<void(Ts...)> {
LazyCallbackManager(LazyCallbackManager &&) = delete;
LazyCallbackManager &operator=(LazyCallbackManager &&) = delete;
/// Add a callback to the list. Allocates the underlying CallbackManager on first use.
void add(std::function<void(Ts...)> &&callback) {
if (!this->callbacks_) {
this->callbacks_ = new CallbackManager<void(Ts...)>();
}
this->callbacks_->add(std::move(callback));
}
/// Add any callable. Allocates the underlying CallbackManager on first use.
template<typename F> void add(F &&callback) { this->add_(Callback<void(Ts...)>::create(std::forward<F>(callback))); }
/// Call all callbacks in this manager. No-op if no callbacks registered.
void call(Ts... args) {
@@ -1809,6 +1855,13 @@ template<typename... Ts> class LazyCallbackManager<void(Ts...)> {
void operator()(Ts... args) { this->call(args...); }
protected:
/// Non-template core to avoid code duplication per lambda type.
void add_(Callback<void(Ts...)> cb) {
if (!this->callbacks_) {
this->callbacks_ = new CallbackManager<void(Ts...)>();
}
this->callbacks_->add_(cb);
}
CallbackManager<void(Ts...)> *callbacks_{nullptr};
};