/** @file callable.hh * * @brief Internal callable data structure. * * There is no public API in this file. * * @copyright See COPYING.md in the project tree for further information. */ #ifndef LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH #define LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH #include #include #include #include #include #include namespace cubescript { namespace internal { /** @private */ template struct callable { private: struct base { base(base const &); base &operator=(base const &); public: base() {} virtual ~base() {} virtual void move_to(base *) = 0; virtual R operator()(A &&...args) const = 0; }; template struct store: base { explicit store(F &&f): p_stor{std::move(f)} {} virtual void move_to(base *p) { ::new (p) store{std::move(p_stor)}; } virtual R operator()(A &&...args) const { return std::invoke(*std::launder( reinterpret_cast(&p_stor) ), std::forward(args)...); } private: F p_stor; }; using alloc_f = void *(*)(void *, void *, std::size_t, std::size_t); struct f_alloc { alloc_f af; void *ud; size_t asize; }; std::aligned_storage_t p_stor; base *p_func; static inline base *as_base(void *p) { return static_cast(p); } template static inline bool f_not_null(T const &) { return true; } template static inline bool f_not_null(T *p) { return !!p; } template static inline bool f_not_null(CR C::*p) { return !!p; } template static inline bool f_not_null(callable const &f) { return !!f; } bool small_storage() { return (static_cast(p_func) == &p_stor); } void cleanup() { if (!p_func) { return; } p_func->~base(); if (!small_storage()) { auto &ad = *std::launder(reinterpret_cast(&p_stor)); ad.af(ad.ud, p_func, ad.asize, 0); } } public: callable() noexcept: p_func{nullptr} {} callable(std::nullptr_t) noexcept: p_func{nullptr} {} callable(std::nullptr_t, alloc_f, void *) noexcept: p_func{nullptr} {} callable(callable &&f) noexcept { if (!f.p_func) { p_func = nullptr; } else if (f.small_storage()) { p_func = as_base(&p_stor); f.p_func->move_to(p_func); } else { p_func = f.p_func; f.p_func = nullptr; } } template callable(F func, alloc_f af, void *ud) { if (!f_not_null(func)) { return; } if constexpr (sizeof(store) <= sizeof(p_stor)) { auto *p = static_cast(&p_stor); p_func = ::new (p) store{std::move(func)}; } else { auto &ad = *std::launder(reinterpret_cast(&p_stor)); ad.af = af; ad.ud = ud; ad.asize = sizeof(store); p_func = static_cast *>( af(ud, nullptr, 0, sizeof(store)) ); try { new (p_func) store{std::move(func)}; } catch (...) { af(ud, p_func, sizeof(store), 0); throw; } } } callable &operator=(callable const &) = delete; callable &operator=(callable &&f) noexcept { cleanup(); if (f.p_func == nullptr) { p_func = nullptr; } else if (f.small_storage()) { p_func = as_base(&p_stor); f.p_func->move_to(p_func); } else { p_func = f.p_func; f.p_func = nullptr; } return *this; } callable &operator=(std::nullptr_t) noexcept { cleanup(); p_func = nullptr; return *this; } template callable &operator=(F &&func) { callable{std::forward(func)}.swap(*this); return *this; } ~callable() { cleanup(); } void swap(callable &f) noexcept { std::aligned_storage_t tmp_stor; if (small_storage() && f.small_storage()) { auto *t = as_base(&tmp_stor); p_func->move_to(t); p_func->~base(); p_func = nullptr; f.p_func->move_to(as_base(&p_stor)); f.p_func->~base(); f.p_func = nullptr; p_func = as_base(&p_stor); t->move_to(as_base(&f.p_stor)); t->~base(); f.p_func = as_base(&f.p_stor); } else if (small_storage()) { /* copy allocator address/size */ std::memcpy(&tmp_stor, &f.p_stor, sizeof(tmp_stor)); p_func->move_to(as_base(&f.p_stor)); p_func->~base(); p_func = f.p_func; f.p_func = as_base(&f.p_stor); std::memcpy(&p_stor, &tmp_stor, sizeof(tmp_stor)); } else if (f.small_storage()) { /* copy allocator address/size */ std::memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor)); f.p_func->move_to(as_base(&p_stor)); f.p_func->~base(); f.p_func = p_func; p_func = as_base(&p_stor); std::memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor)); } else { /* copy allocator address/size */ std::memcpy(&tmp_stor, &p_stor, sizeof(tmp_stor)); std::memcpy(&p_stor, &f.p_stor, sizeof(tmp_stor)); std::memcpy(&f.p_stor, &tmp_stor, sizeof(tmp_stor)); std::swap(p_func, f.p_func); } } explicit operator bool() const noexcept { return !!p_func; } R operator()(A ...args) const { return (*p_func)(std::forward(args)...); } }; } /* namespace internal */ } /* namespace cubescript */ #endif /* LIBCUBESCRIPT_CUBESCRIPT_CALLABLE_HH */