diff --git a/esphome/core/template_lambda.h b/esphome/core/template_lambda.h index 1977265c33..894b5edffa 100644 --- a/esphome/core/template_lambda.h +++ b/esphome/core/template_lambda.h @@ -1,120 +1,51 @@ #pragma once -#include -#include #include "esphome/core/optional.h" namespace esphome { -/** Helper class for template platforms that stores either a stateless lambda (function pointer) - * or a stateful lambda (std::function pointer). +/** Lightweight wrapper for template platform lambdas (stateless function pointers only). * - * This provides backward compatibility with PR #11555 while maintaining the optimization: - * - Stateless lambdas (no capture) → function pointer (4 bytes on ESP32) - * - Stateful lambdas (with capture) → pointer to std::function (4 bytes on ESP32) - * Total size: enum (1 byte) + union (4 bytes) + padding = 8 bytes (same as PR #11555) + * This optimizes template platforms by storing only a function pointer (4 bytes on ESP32) + * instead of std::function (16-32 bytes). * - * Both lambda types must return optional (as YAML codegen does) to support the pattern: - * return {}; // Don't publish a value + * IMPORTANT: This only supports stateless lambdas (no captures). The set_template() method + * is an internal API used by YAML codegen, not intended for external use. + * + * Lambdas must return optional to support the pattern: + * return {}; // Don't publish a value * return 42.0; // Publish this value * - * operator() returns optional, returning nullopt when no lambda is set (type == NONE). - * This eliminates redundant "is lambda set" checks by reusing optional's discriminator. + * operator() returns optional, returning nullopt when no lambda is set (nullptr check). * - * @tparam T The return type (e.g., float for TemplateLambda>) + * @tparam T The return type (e.g., float for sensor values) * @tparam Args Optional arguments for the lambda */ template class TemplateLambda { public: - TemplateLambda() : type_(NONE) {} + TemplateLambda() : f_(nullptr) {} - // For stateless lambdas: use function pointer - template - requires std::invocable && std::convertible_to < F, optional(*) - (Args...) > void set(F f) { - this->reset_(); - this->type_ = STATELESS_LAMBDA; - this->stateless_f_ = f; // Implicit conversion to function pointer - } + /** Set the lambda function pointer. + * INTERNAL API: Only for use by YAML codegen. + * Only stateless lambdas (no captures) are supported. + */ + void set(optional (*f)(Args...)) { this->f_ = f; } - // For stateful lambdas: use std::function pointer - template - requires std::invocable && - (!std::convertible_to (*)(Args...)>) &&std::convertible_to, - optional> void set(F &&f) { - this->reset_(); - this->type_ = LAMBDA; - this->f_ = new std::function(Args...)>(std::forward(f)); - } + /** Check if a lambda is set */ + bool has_value() const { return this->f_ != nullptr; } - ~TemplateLambda() { this->reset_(); } - - // Copy constructor - TemplateLambda(const TemplateLambda &) = delete; - TemplateLambda &operator=(const TemplateLambda &) = delete; - - // Move constructor - TemplateLambda(TemplateLambda &&other) noexcept : type_(other.type_) { - if (type_ == LAMBDA) { - this->f_ = other.f_; - other.f_ = nullptr; - } else if (type_ == STATELESS_LAMBDA) { - this->stateless_f_ = other.stateless_f_; - } - other.type_ = NONE; - } - - TemplateLambda &operator=(TemplateLambda &&other) noexcept { - if (this != &other) { - this->reset_(); - this->type_ = other.type_; - if (type_ == LAMBDA) { - this->f_ = other.f_; - other.f_ = nullptr; - } else if (type_ == STATELESS_LAMBDA) { - this->stateless_f_ = other.stateless_f_; - } - other.type_ = NONE; - } - return *this; - } - - bool has_value() const { return this->type_ != NONE; } - - // Returns optional, returning nullopt if no lambda is set + /** Call the lambda, returning nullopt if no lambda is set */ optional operator()(Args... args) { - switch (this->type_) { - case STATELESS_LAMBDA: - return this->stateless_f_(args...); // Direct function pointer call - case LAMBDA: - return (*this->f_)(args...); // std::function call via pointer - case NONE: - default: - return nullopt; // No lambda set - } + if (this->f_ == nullptr) + return nullopt; + return this->f_(args...); } + /** Alias for operator() for compatibility */ optional call(Args... args) { return (*this)(args...); } protected: - void reset_() { - if (this->type_ == LAMBDA) { - delete this->f_; - this->f_ = nullptr; - } - this->type_ = NONE; - } - - enum : uint8_t { - NONE, - STATELESS_LAMBDA, - LAMBDA, - } type_; - - union { - optional (*stateless_f_)(Args...); // Function pointer (4 bytes on ESP32) - std::function(Args...)> *f_; // Pointer to std::function (4 bytes on ESP32) - }; + optional (*f_)(Args...); // Function pointer (4 bytes on ESP32) }; } // namespace esphome diff --git a/tests/components/template/common-base.yaml b/tests/components/template/common-base.yaml index 7d2cb19077..f101eea942 100644 --- a/tests/components/template/common-base.yaml +++ b/tests/components/template/common-base.yaml @@ -10,26 +10,12 @@ esphome: state: !lambda "return 42.0;" # Test C++ API: set_template() with stateless lambda (no captures) + # NOTE: set_template() is not intended to be a public API, but we test it to ensure it doesn't break. - lambda: |- id(template_sens).set_template([]() -> esphome::optional { return 123.0f; }); - # Test C++ API: set_template() with stateful lambda (with captures) - # This is the regression test for issue #11555 - - lambda: |- - float captured_value = 456.0f; - id(template_sens).set_template([captured_value]() -> esphome::optional { - return captured_value; - }); - - # Test C++ API: set_template() with more complex capture - - lambda: |- - auto sensor_id = id(template_sens); - id(template_number).set_template([sensor_id]() -> esphome::optional { - return sensor_id->state * 2.0f; - }); - - datetime.date.set: id: test_date date: