#pragma once #include "fl/memory.h" #include "fl/type_traits.h" #include "fl/compiler_control.h" #include "fl/variant.h" #include "fl/memfill.h" #include "fl/type_traits.h" #include "fl/inplacenew.h" #include "fl/bit_cast.h" #include "fl/align.h" #ifndef FASTLED_INLINE_LAMBDA_SIZE #define FASTLED_INLINE_LAMBDA_SIZE 64 #endif FL_DISABLE_WARNING_PUSH FL_DISABLE_WARNING(float-equal) namespace fl { //---------------------------------------------------------------------------- // is_function_pointer trait - detects function pointers like R(*)(Args...) //---------------------------------------------------------------------------- template struct is_function_pointer { static constexpr bool value = false; }; template struct is_function_pointer { static constexpr bool value = true; }; //---------------------------------------------------------------------------- // More or less a drop in replacement for std::function // function: type‐erasing "std::function" replacement // Supports free functions, lambdas/functors, member functions (const & // non‑const) // // NEW: Uses inline storage for member functions, free functions, and small // lambdas/functors. Only large lambdas/functors use heap allocation. //---------------------------------------------------------------------------- template class function; template class FL_ALIGN function { private: struct CallableBase { virtual R invoke(Args... args) = 0; virtual ~CallableBase() = default; }; template struct Callable : CallableBase { F f; Callable(F fn) : f(fn) {} R invoke(Args... args) override { return f(args...); } }; // Type-erased free function callable - stored inline! struct FreeFunctionCallable { R (*func_ptr)(Args...); FreeFunctionCallable(R (*fp)(Args...)) : func_ptr(fp) {} R invoke(Args... args) const { return func_ptr(args...); } }; // Type-erased small lambda/functor callable - stored inline! // Size limit for inline storage - configurable via preprocessor define static constexpr fl::size kInlineLambdaSize = FASTLED_INLINE_LAMBDA_SIZE; struct InlinedLambda { // Storage for the lambda/functor object // Use aligned storage to ensure proper alignment for any type alignas(max_align_t) char bytes[kInlineLambdaSize]; // Type-erased invoker and destructor function pointers R (*invoker)(const InlinedLambda& storage, Args... args); void (*destructor)(InlinedLambda& storage); template InlinedLambda(Function f) { static_assert(sizeof(Function) <= kInlineLambdaSize, "Lambda/functor too large for inline storage"); static_assert(alignof(Function) <= alignof(max_align_t), "Lambda/functor requires stricter alignment than storage provides"); // Initialize the entire storage to zero to avoid copying uninitialized memory fl::memfill(bytes, 0, kInlineLambdaSize); // Construct the lambda/functor in-place new (bytes) Function(fl::move(f)); // Set up type-erased function pointers invoker = &invoke_lambda; destructor = &destroy_lambda; } // Copy constructor InlinedLambda(const InlinedLambda& other) : invoker(other.invoker), destructor(other.destructor) { // This is tricky - we need to copy the stored object // For now, we'll use memcopy (works for trivially copyable types) fl::memcopy(bytes, other.bytes, kInlineLambdaSize); } // Move constructor InlinedLambda(InlinedLambda&& other) : invoker(other.invoker), destructor(other.destructor) { fl::memcopy(bytes, other.bytes, kInlineLambdaSize); // Reset the other object to prevent double destruction other.destructor = nullptr; } ~InlinedLambda() { if (destructor) { destructor(*this); } } template static R invoke_lambda(const InlinedLambda& storage, Args... args) { // Use placement new to safely access the stored lambda alignas(FUNCTOR) char temp_storage[sizeof(FUNCTOR)]; // Copy the lambda from storage fl::memcopy(temp_storage, storage.bytes, sizeof(FUNCTOR)); // Get a properly typed pointer to the copied lambda (non-const for mutable lambdas) FUNCTOR* f = static_cast(static_cast(temp_storage)); // Invoke the lambda return (*f)(args...); } template static void destroy_lambda(InlinedLambda& storage) { // For destruction, we need to call the destructor on the actual object // that was constructed with placement new in storage.bytes // We use the standard library approach: create a properly typed pointer // using placement new, then call the destructor through that pointer // This is the standard-compliant way to get a properly typed pointer // to an object that was constructed with placement new FUNCTOR* obj_ptr = static_cast(static_cast(storage.bytes)); obj_ptr->~FUNCTOR(); } R invoke(Args... args) const { return invoker(*this, args...); } }; // Type-erased member function callable base struct MemberCallableBase { virtual R invoke(Args... args) const = 0; virtual ~MemberCallableBase() = default; }; // Type-erased non-const member function callable struct NonConstMemberCallable : MemberCallableBase { void* obj; // Union to store member function pointer as raw bytes union MemberFuncStorage { char bytes[sizeof(R (NonConstMemberCallable::*)(Args...))]; // Ensure proper alignment void* alignment_dummy; } member_func_storage; // Type-erased invoker function - set at construction time R (*invoker)(void* obj, const MemberFuncStorage& mfp, Args... args); template NonConstMemberCallable(C* o, R (C::*mf)(Args...)) : obj(o) { // Store the member function pointer as raw bytes static_assert(sizeof(mf) <= sizeof(member_func_storage), "Member function pointer too large"); fl::memcopy(member_func_storage.bytes, &mf, sizeof(mf)); // Set the invoker to a function that knows how to cast back and call invoker = &invoke_nonconst_member; } template static R invoke_nonconst_member(void* obj, const MemberFuncStorage& mfp, Args... args) { C* typed_obj = static_cast(obj); R (C::*typed_mf)(Args...); fl::memcopy(&typed_mf, mfp.bytes, sizeof(typed_mf)); return (typed_obj->*typed_mf)(args...); } R invoke(Args... args) const override { return invoker(obj, member_func_storage, args...); } }; // Type-erased const member function callable struct ConstMemberCallable : MemberCallableBase { const void* obj; // Union to store member function pointer as raw bytes union MemberFuncStorage { char bytes[sizeof(R (ConstMemberCallable::*)(Args...) const)]; // Ensure proper alignment void* alignment_dummy; } member_func_storage; // Type-erased invoker function - set at construction time R (*invoker)(const void* obj, const MemberFuncStorage& mfp, Args... args); template ConstMemberCallable(const C* o, R (C::*mf)(Args...) const) : obj(o) { // Store the member function pointer as raw bytes static_assert(sizeof(mf) <= sizeof(member_func_storage), "Member function pointer too large"); fl::memcopy(member_func_storage.bytes, &mf, sizeof(mf)); // Set the invoker to a function that knows how to cast back and call invoker = &invoke_const_member; } template static R invoke_const_member(const void* obj, const MemberFuncStorage& mfp, Args... args) { const C* typed_obj = static_cast(obj); R (C::*typed_mf)(Args...) const; fl::memcopy(&typed_mf, mfp.bytes, sizeof(typed_mf)); return (typed_obj->*typed_mf)(args...); } R invoke(Args... args) const override { return invoker(obj, member_func_storage, args...); } }; // Variant to store any of our callable types inline (with heap fallback for large lambdas) using Storage = Variant, FreeFunctionCallable, InlinedLambda, NonConstMemberCallable, ConstMemberCallable>; Storage storage_; // Helper function to handle default return value for void and non-void types template typename enable_if::value, ReturnType>::type default_return_helper() const { return ReturnType{}; } template typename enable_if::value, ReturnType>::type default_return_helper() const { return; } public: function() = default; // Copy constructor - properly handle Variant alignment function(const function& other) : storage_(other.storage_) {} // Move constructor - properly handle Variant alignment function(function&& other) noexcept : storage_(fl::move(other.storage_)) {} // Copy assignment function& operator=(const function& other) { if (this != &other) { storage_ = other.storage_; } return *this; } // Move assignment function& operator=(function&& other) noexcept { if (this != &other) { storage_ = fl::move(other.storage_); } return *this; } // 1) Free function constructor - stored inline! function(R (*fp)(Args...)) { storage_ = FreeFunctionCallable(fp); } // 2) Lambda/functor constructor - inline if small, heap if large template ::value && !is_function_pointer::value>> function(F f) { // Use template specialization instead of if constexpr for C++14 compatibility construct_lambda_or_functor(fl::move(f), typename conditional::type{}); } // 3) non‑const member function - stored inline! template function(R (C::*mf)(Args...), C* obj) { storage_ = NonConstMemberCallable(obj, mf); } // 4) const member function - stored inline! template function(R (C::*mf)(Args...) const, const C* obj) { storage_ = ConstMemberCallable(obj, mf); } R operator()(Args... args) const { // Direct dispatch using type checking - efficient and simple if (auto* heap_callable = storage_.template ptr>()) { return (*heap_callable)->invoke(args...); } else if (auto* free_func = storage_.template ptr()) { return free_func->invoke(args...); } else if (auto* inlined_lambda = storage_.template ptr()) { return inlined_lambda->invoke(args...); } else if (auto* nonconst_member = storage_.template ptr()) { return nonconst_member->invoke(args...); } else if (auto* const_member = storage_.template ptr()) { return const_member->invoke(args...); } // This should never happen if the function is properly constructed return default_return_helper(); } explicit operator bool() const { return !storage_.empty(); } void clear() { storage_ = Storage{}; // Reset to empty variant } bool operator==(const function& o) const { // For simplicity, just check if both are empty or both are non-empty // Full equality would require more complex comparison logic return storage_.empty() == o.storage_.empty(); } bool operator!=(const function& o) const { return !(*this == o); } private: // Helper for small lambdas/functors - inline storage template void construct_lambda_or_functor(F f, true_type /* small */) { storage_ = InlinedLambda(fl::move(f)); } // Helper for large lambdas/functors - heap storage template void construct_lambda_or_functor(F f, false_type /* large */) { storage_ = fl::shared_ptr(fl::make_shared>(fl::move(f))); } }; } // namespace fl FL_DISABLE_WARNING_POP