[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<uint8_t>. The union's value_ lifetime is managed
externally via placement new and destroy_().

Reverts esp32_ble_server back to TEMPLATABLE_VALUE macro.
This commit is contained in:
J. Nick Koston
2026-04-07 19:56:41 -10:00
parent ee83f525c4
commit 49b2882f2b
+23 -16
View File
@@ -105,11 +105,13 @@ template<typename T, typename... X> class TemplatableValue {
// Accept raw constants
template<typename V> TemplatableValue(V value) requires(!std::invocable<V, X...>) : tag_(VALUE) {
new (&this->value_) T(static_cast<T>(std::move(value)));
new (&this->storage_.value_) T(static_cast<T>(std::move(value)));
}
// Accept stateless lambdas (convertible to function pointer)
template<typename F> TemplatableValue(F f) requires std::convertible_to<F, T (*)(X...)> : tag_(FN) { this->f_ = f; }
template<typename F> TemplatableValue(F f) requires std::convertible_to<F, T (*)(X...)> : tag_(FN) {
this->storage_.f_ = f;
}
// Convertible return type (e.g., int -> uint8_t) — casting trampoline
template<typename F>
@@ -117,7 +119,7 @@ template<typename T, typename... X> class TemplatableValue {
"codegen")]] TemplatableValue(F) requires(!std::convertible_to<F, T (*)(X...)>) &&
std::invocable<F, X...> &&std::convertible_to<std::invoke_result_t<F, X...>, T> &&std::is_empty_v<F>
&&std::default_initializable<F> : tag_(FN) {
this->f_ = [](X... x) -> T { return static_cast<T>(F{}(x...)); };
this->storage_.f_ = [](X... x) -> T { return static_cast<T>(F{}(x...)); };
}
// Reject any callable that didn't match the above
@@ -129,18 +131,18 @@ template<typename T, typename... X> 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<typename T, typename... X> 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<typename T, typename... X> 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<typename T, typename... X> 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<typename T, typename... X> class TemplatableValue {
void destroy_() {
if constexpr (!std::is_trivially_destructible_v<T>) {
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<uint8_t>). 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,