// Copyright 2025 Steven Le Rouzic // // SPDX-License-Identifier: BSD-3-Clause #pragma once #include "asl/base/utility.hpp" #include "asl/base/meta.hpp" #include "asl/base/integers.hpp" #include "asl/memory/allocator.hpp" #include "asl/base/functional.hpp" namespace asl { namespace function_detail { static constexpr isize_t kStorageSize = size_of * 2; struct Storage { alignas(align_of) byte raw[kStorageSize]; [[nodiscard]] void* get_ptr() const { // NOLINTNEXTLINE(*-const-cast) return const_cast(static_cast(raw)); } }; template concept can_be_stored_inline = size_of <= size_of && align_of % align_of == 0; enum class FunctionOp : uint8_t { kDestroyThis, kCopyFromOtherToThisUninit, kMoveFromOtherToThisUninit, }; template> struct FunctionImplBase { using Allocator = DefaultAllocator; template static void create(Storage* storage, T&& t) { Allocator allocator{}; auto* ptr = alloc_new(allocator, std::forward(t)); asl::memcpy(storage->get_ptr(), static_cast(&ptr), size_of); } static Functor** get_functor_ptr(const Storage* storage) { // NOLINTNEXTLINE(*-reinterpret-cast) return std::launder(reinterpret_cast(storage->get_ptr())); } static Functor* get_functor(const Storage* storage) { return *get_functor_ptr(storage); } static void op(Storage* this_storage, Storage* other_storage, FunctionOp op) { switch (op) { using enum FunctionOp; case kDestroyThis: { Allocator allocator{}; alloc_delete(allocator, get_functor(this_storage)); break; } case kCopyFromOtherToThisUninit: { create(this_storage, *static_cast(get_functor(other_storage))); break; } case kMoveFromOtherToThisUninit: { auto* ptr = asl::exchange(*get_functor_ptr(other_storage), nullptr); asl::memcpy(this_storage->get_ptr(), static_cast(&ptr), size_of); break; } default: break; } } }; template struct FunctionImplBase { template static void create(Storage* storage, T&& t) { new (storage->get_ptr()) Functor(std::forward(t)); } static Functor* get_functor(const Storage* storage) { // NOLINTNEXTLINE(*-reinterpret-cast) return std::launder(reinterpret_cast(storage->get_ptr())); } static void op(Storage* this_storage, Storage* other_storage, FunctionOp op) { switch (op) { using enum FunctionOp; case kDestroyThis: { destroy(get_functor(this_storage)); break; } case kCopyFromOtherToThisUninit: { create(this_storage, *static_cast(get_functor(other_storage))); break; } case kMoveFromOtherToThisUninit: { auto* other_functor = get_functor(other_storage); create(this_storage, std::move(*static_cast(other_functor))); destroy(other_functor); break; } default: break; } } }; template struct FunctionImpl : FunctionImplBase { static R invoke(Args... args, const Storage& storage) { auto* functor = FunctionImplBase::get_functor(&storage); return asl::invoke(*functor, std::forward(args)...); } }; template concept valid_functor = copy_constructible && move_constructible && invocable && same_as>; } // namespace function_detail template class function; template class function // NOLINT(*-member-init) { using InvokeFn = R (*)(Args..., const function_detail::Storage&); using OpFn = void (*)(function_detail::Storage*, function_detail::Storage*, function_detail::FunctionOp); function_detail::Storage m_storage; InvokeFn m_invoke{}; OpFn m_op{}; void destroy() { if (m_op != nullptr) { (*m_op)(&m_storage, nullptr, function_detail::FunctionOp::kDestroyThis); } } public: function() = default; template function(T&& func) // NOLINT(*explicit*,*-member-init) requires ( !same_as> && function_detail::valid_functor ) { using Functor = decay_t; using Impl = function_detail::FunctionImpl; Impl::create(&m_storage, std::forward(func)); m_invoke = &Impl::invoke; // NOLINT(*-member-initializer) m_op = &Impl::op; // NOLINT(*-member-initializer) } function(const function& other) // NOLINT(*-member-init) : m_invoke{other.m_invoke} , m_op{other.m_op} { if (m_op != nullptr) { m_op( &m_storage, const_cast(&other.m_storage), // NOLINT(*-const-cast) function_detail::FunctionOp::kCopyFromOtherToThisUninit); } } function(function&& other) // NOLINT(*-member-init) : m_invoke{asl::exchange(other.m_invoke, nullptr)} , m_op{asl::exchange(other.m_op, nullptr)} { if (m_op != nullptr) { m_op( &m_storage, &other.m_storage, function_detail::FunctionOp::kMoveFromOtherToThisUninit); } } ~function() { destroy(); } function& operator=(const function& other) { if (this != &other) { destroy(); m_invoke = other.m_invoke; m_op = other.m_op; m_op( &m_storage, const_cast(&other.m_storage), // NOLINT(*-const-cast) function_detail::FunctionOp::kCopyFromOtherToThisUninit); } return *this; } function& operator=(function&& other) { if (this != &other) { destroy(); m_invoke = asl::exchange(other.m_invoke, nullptr); m_op = asl::exchange(other.m_op, nullptr); m_op( &m_storage, &other.m_storage, function_detail::FunctionOp::kMoveFromOtherToThisUninit); } return *this; } template function& operator=(T&& func) requires ( !same_as> && function_detail::valid_functor ) { destroy(); using Functor = decay_t; using Impl = function_detail::FunctionImpl; Impl::create(&m_storage, std::forward(func)); m_invoke = &Impl::invoke; m_op = &Impl::op; return *this; } constexpr R operator()(Args... args) const { ASL_ASSERT(m_invoke); return m_invoke(args..., m_storage); } }; } // namespace asl