diff --git a/esphome/core/application.h b/esphome/core/application.h index 44e8de7ee9f..b8c8f1370ad 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -118,6 +118,13 @@ void original_setup(); // NOLINT(readability-redundant-declaration) - used by c namespace esphome { +/// SFINAE helper: resolves to true when &T::loop compiles and differs from &Component::loop. +/// Falls back to true when &T::loop is ambiguous (e.g. multiple inheritance with separate loop() methods). +template struct has_loop_override : std::true_type {}; +template +struct has_loop_override> + : std::bool_constant> {}; + // Teardown timeout constant (in milliseconds) // For reboots, it's more important to shut down quickly than disconnect cleanly // since we're not entering deep sleep. The only consequence of not shutting down @@ -544,10 +551,9 @@ class Application { #endif /// Register a component, detecting loop() override at compile time. - /// The template resolves &T::loop vs &Component::loop as a constexpr bool - /// and forwards it to register_component_impl_ which stores it in component_state_. + /// Uses has_loop_override which handles ambiguous &T::loop from multiple inheritance. template void register_component_(T *comp) { - this->register_component_impl_(comp, !std::is_same_v); + this->register_component_impl_(comp, has_loop_override::value); } void register_component_impl_(Component *comp, bool has_loop); diff --git a/esphome/core/config.py b/esphome/core/config.py index 3835fd3875f..18d3648cb83 100644 --- a/esphome/core/config.py +++ b/esphome/core/config.py @@ -517,9 +517,10 @@ async def _add_looping_components() -> None: return # Build constexpr sum for the exact count, deduplicating by type + # Uses has_loop_override which handles ambiguous &T::loop from multiple inheritance type_counts = Counter(entries) terms = [ - f"({count} * !std::is_same_v)" + f"({count} * has_loop_override<{cpp_type}>::value)" for cpp_type, count in type_counts.items() ] constexpr_expr = " + \\\n ".join(terms)