From 5ed3be7b9748c8e26782c7e429d19aa158039fca Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Mon, 2 Mar 2026 07:49:44 -1000 Subject: [PATCH] [core] Fix compile-time loop() detection for multiple inheritance The compile-time loop() override detection using decltype(&T::loop) fails when a component inherits from both Component (via PollingComponent) and BLEClientNode, as both define loop() methods making the expression ambiguous. Add a SFINAE-based has_loop_override trait that gracefully handles this case: when decltype(&T::loop) is ill-formed due to ambiguity, it falls back to true (conservatively assuming a loop override exists). --- esphome/core/application.h | 12 +++++++++--- esphome/core/config.py | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) 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)