From eaf38c10ccc86e755ea1bf909b60566909165243 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 22 Mar 2026 18:40:00 -1000 Subject: [PATCH] [binary_sensor] Use const T* instead of T current{} in set_new_state Avoids requiring T to be default-constructible, which would break future instantiations with non-default-constructible state types. Capture old_state before set_state_value() since the pointer aliases subclass storage that gets overwritten. --- esphome/core/entity_base.h | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 869981eb7a..d03326ccf8 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -347,27 +347,26 @@ template class StatefulEntityBase : public EntityBase { virtual bool set_new_state(const optional &new_state) { // Access flags_ directly to avoid function call overhead in this hot path bool had_state = this->flags_.has_state; - // Cache get_state() result to avoid calling the virtual method twice - T current{}; - if (had_state) - current = this->get_state(); + // Use pointer to avoid requiring T to be default-constructible + const T *current = had_state ? &this->get_state() : nullptr; if (new_state.has_value()) { - if (had_state && current == new_state.value()) + if (current != nullptr && *current == new_state.value()) return false; // same value, no change } else if (!had_state) { return false; // already invalidated, no change } - // State changed — update storage before firing callbacks so callback code - // can inspect the entity's current state via get_state()/has_state() + // Capture old_state before set_state_value — current pointer aliases subclass storage + bool has_full_cbs = !this->full_state_callbacks_.empty(); + optional old_state; + if (has_full_cbs) + old_state = current != nullptr ? optional(*current) : nullopt; + // Update storage before firing callbacks so callback code can inspect current state this->flags_.has_state = new_state.has_value(); if (new_state.has_value()) { this->set_state_value(new_state.value()); } - // Only construct old_state and call full_state_callbacks when callbacks are registered - if (!this->full_state_callbacks_.empty()) { - optional old_state = had_state ? optional(current) : nullopt; + if (has_full_cbs) this->full_state_callbacks_.call(old_state, new_state); - } // had_state first: on every change except the first, skips the virtual call if (new_state.has_value() && (had_state || this->get_trigger_on_initial_state())) this->state_callbacks_.call(new_state.value());