From 49b2882f2b9aad9039447d7e586c8f35b46c9756 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 7 Apr 2026 19:56:41 -1000 Subject: [PATCH] [core] Fix TemplatableValue union for non-trivially-constructible types Add explicit Storage union with trivial ctor/dtor so that TemplatableValue works with non-trivially-constructible types like std::vector. The union's value_ lifetime is managed externally via placement new and destroy_(). Reverts esp32_ble_server back to TEMPLATABLE_VALUE macro. --- esphome/core/automation.h | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 5a29d61857c..a574872bb23 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -105,11 +105,13 @@ template class TemplatableValue { // Accept raw constants template TemplatableValue(V value) requires(!std::invocable) : tag_(VALUE) { - new (&this->value_) T(static_cast(std::move(value))); + new (&this->storage_.value_) T(static_cast(std::move(value))); } // Accept stateless lambdas (convertible to function pointer) - template TemplatableValue(F f) requires std::convertible_to : tag_(FN) { this->f_ = f; } + template TemplatableValue(F f) requires std::convertible_to : tag_(FN) { + this->storage_.f_ = f; + } // Convertible return type (e.g., int -> uint8_t) — casting trampoline template @@ -117,7 +119,7 @@ template class TemplatableValue { "codegen")]] TemplatableValue(F) requires(!std::convertible_to) && std::invocable &&std::convertible_to, T> &&std::is_empty_v &&std::default_initializable : tag_(FN) { - this->f_ = [](X... x) -> T { return static_cast(F{}(x...)); }; + this->storage_.f_ = [](X... x) -> T { return static_cast(F{}(x...)); }; } // Reject any callable that didn't match the above @@ -129,18 +131,18 @@ template class TemplatableValue { TemplatableValue(const TemplatableValue &other) : tag_(other.tag_) { if (this->tag_ == VALUE) { - new (&this->value_) T(other.value_); + new (&this->storage_.value_) T(other.storage_.value_); } else if (this->tag_ == FN) { - this->f_ = other.f_; + this->storage_.f_ = other.storage_.f_; } } TemplatableValue(TemplatableValue &&other) noexcept : tag_(other.tag_) { if (this->tag_ == VALUE) { - new (&this->value_) T(std::move(other.value_)); + new (&this->storage_.value_) T(std::move(other.storage_.value_)); other.destroy_(); } else if (this->tag_ == FN) { - this->f_ = other.f_; + this->storage_.f_ = other.storage_.f_; } other.tag_ = NONE; } @@ -150,9 +152,9 @@ template class TemplatableValue { this->destroy_(); this->tag_ = other.tag_; if (this->tag_ == VALUE) { - new (&this->value_) T(other.value_); + new (&this->storage_.value_) T(other.storage_.value_); } else if (this->tag_ == FN) { - this->f_ = other.f_; + this->storage_.f_ = other.storage_.f_; } } return *this; @@ -163,10 +165,10 @@ template class TemplatableValue { this->destroy_(); this->tag_ = other.tag_; if (this->tag_ == VALUE) { - new (&this->value_) T(std::move(other.value_)); + new (&this->storage_.value_) T(std::move(other.storage_.value_)); other.destroy_(); } else if (this->tag_ == FN) { - this->f_ = other.f_; + this->storage_.f_ = other.storage_.f_; } other.tag_ = NONE; } @@ -179,9 +181,9 @@ template class TemplatableValue { T value(X... x) const { if (this->tag_ == FN) - return this->f_(x...); + return this->storage_.f_(x...); if (this->tag_ == VALUE) - return this->value_; + return this->storage_.value_; return T{}; } @@ -201,15 +203,20 @@ template class TemplatableValue { void destroy_() { if constexpr (!std::is_trivially_destructible_v) { if (this->tag_ == VALUE) - this->value_.~T(); + this->storage_.value_.~T(); } } enum Tag : uint8_t { NONE, VALUE, FN } tag_{NONE}; - union { + // Union with explicit ctor/dtor to support non-trivially-constructible/destructible T + // (e.g., std::vector). Lifetime of value_ is managed externally via + // placement new and destroy_(). + union Storage { + constexpr Storage() : f_(nullptr) {} + constexpr ~Storage() {} T value_; T (*f_)(X...); - }; + } storage_; }; /// Specialization for std::string: supports VALUE, STATIC_STRING, FLASH_STRING,