[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<T> 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).
This commit is contained in:
J. Nick Koston
2026-03-02 07:50:34 -10:00
parent b9b1af1c3d
commit 5ed3be7b97
2 changed files with 11 additions and 4 deletions
+9 -3
View File
@@ -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<typename T, typename = void> struct has_loop_override : std::true_type {};
template<typename T>
struct has_loop_override<T, std::void_t<decltype(&T::loop)>>
: std::bool_constant<!std::is_same_v<decltype(&T::loop), decltype(&Component::loop)>> {};
// 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<T> which handles ambiguous &T::loop from multiple inheritance.
template<typename T> void register_component_(T *comp) {
this->register_component_impl_(comp, !std::is_same_v<decltype(&T::loop), decltype(&Component::loop)>);
this->register_component_impl_(comp, has_loop_override<T>::value);
}
void register_component_impl_(Component *comp, bool has_loop);
+2 -1
View File
@@ -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<T> which handles ambiguous &T::loop from multiple inheritance
type_counts = Counter(entries)
terms = [
f"({count} * !std::is_same_v<decltype(&{cpp_type}::loop), decltype(&Component::loop)>)"
f"({count} * has_loop_override<{cpp_type}>::value)"
for cpp_type, count in type_counts.items()
]
constexpr_expr = " + \\\n ".join(terms)