#pragma once #include "gp/exception.hpp" #include "gp/algorithm/modifiers.hpp" #include "gp/algorithm/move.hpp" #include "gp/algorithm/tmp_manip.hpp" #include "gp/allocator/allocator.hpp" #include "gp/optional.hpp" namespace gp{ namespace details { template auto ensure(pred a, returner b) { gp_config::assertion(a(), "could not ensure predicate"); return b(); } } template class function; template class function{ using fn_ptr = char*; using invoke_fn_t = ret (*)(fn_ptr, args&&...); using condestruct_fn_t = void (*) (fn_ptr, fn_ptr); gp::optional> alloc; invoke_fn_t invokator; condestruct_fn_t condestructor; fn_ptr data_ptr; size_t data_size; template static ret invoke(func* fn, args&&... fw_args) { return (*fn)(gp::forward(fw_args)...); } template static void condestruct(func* dest, func* src) { if(dest != nullptr) { new (dest) func(*src); } else { src->~func(); } } static void nop_condestruct(char*, char*) { return; } public: #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnull-dereference" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnull-dereference" function(allocator& alloc_v) #pragma GCC diagnostic pop #pragma clang diagnostic pop : alloc(alloc_v) , invokator(nullptr) , condestructor(nullptr) , data_ptr(nullptr) , data_size(0) {} template function(func f, allocator& alloc_v) : alloc(alloc_v) , invokator(reinterpret_cast(invoke)) , condestructor(reinterpret_cast(condestruct)) , data_ptr( sizeof(func) <= sizeof(data_ptr) ? 0 : details::ensure( [&](){return alloc.has_value();}, [&](){return (char*)(alloc.value().get().allocate(sizeof(func)));} ) ) , data_size( sizeof(func) <= sizeof(data_ptr) ? 0 : sizeof(func) ) { gp_config::assertion(!(alloc.has_value() && data_ptr == nullptr), "allocator failed in function"); if(data_ptr != nullptr) this->condestructor(data_size < sizeof(data_ptr) ? (char*)&data_ptr : data_ptr, reinterpret_cast(&f)); } template function(func f, nullopt_t alloc_v = nullopt) : alloc(alloc_v) , invokator(reinterpret_cast(invoke)) , condestructor(reinterpret_cast(condestruct)) , data_ptr( sizeof(func) <= sizeof(data_ptr) ? 0 : details::ensure( [&](){return alloc.has_value();}, [&](){return (char*)(alloc.value().get().allocate(sizeof(func)));} ) ) , data_size( sizeof(func) <= sizeof(data_ptr) ? 0 : sizeof(func) ) { gp_config::assertion(!(alloc.has_value() && data_ptr == nullptr), "allocator failed in function"); if(data_ptr != nullptr) this->condestructor(data_size < sizeof(data_ptr) ? (char*)&data_ptr : data_ptr, reinterpret_cast(&f)); } function(function const& rhs) : alloc(rhs.alloc) , invokator(rhs.invokator) , condestructor(rhs.condestructor) , data_ptr( rhs.data_size <= sizeof(data_ptr) ? 0 : details::ensure( [&](){return alloc.has_value();}, [&](){return (char*)(alloc.value().get().allocate(rhs.data_size));} ) ) , data_size(rhs.data_size) { gp_config::assertion(!(alloc.has_value() && data_ptr == nullptr), "allocator failed in function"); if( data_ptr != nullptr and rhs.data_ptr != nullptr ) this->condestructor(data_size <= sizeof(data_ptr) ? (char*)&data_ptr : data_ptr, rhs.data_ptr); } function(function&& rhs) : alloc(rhs.alloc) , invokator(rhs.invokator) , condestructor(rhs.condestructor) , data_ptr(data_size <= sizeof(data_ptr) ? 0 : rhs.data_ptr) , data_size(rhs.data_size) { if(data_size <= sizeof(data_ptr) && data_size != 0 && data_ptr != nullptr && alloc.has_value()) { condestructor((char*)&data_ptr, (char*)&rhs.data_ptr); condestructor(nullptr, (char*)&rhs.data_ptr); } rhs.data_ptr = nullptr; } ~function(){ if(data_size == 0) { return; } if(data_size <= 0) { return; } if(data_ptr != nullptr && alloc.has_value()) { condestructor(nullptr, data_ptr); alloc.value().get().deallocate(data_ptr); data_ptr = nullptr; } if(data_size < sizeof(data_ptr)) { condestructor(nullptr, (char*)&data_ptr); } } function& operator=(function&& rhs) { gp::swap(alloc, rhs.alloc); gp::swap(invokator, rhs.invokator); gp::swap(condestructor, rhs.condestructor); gp::swap(data_ptr,rhs.data_ptr); gp::swap(data_size,rhs.data_size); return *this; } function& operator=(function& rhs) { alloc = rhs.alloc; invokator = rhs.invokator; condestructor = rhs.condestructor; condestructor(nullptr, data_ptr); alloc.value().get().deallocate(data_ptr); data_ptr = rhs.data_size <= sizeof(data_ptr) ? 0 : details::ensure( [&](){return alloc.has_value();}, [&](){return (char*)(alloc.value().get().allocate(rhs.data_size));} ); data_size = rhs.data_size; gp_config::assertion(!(alloc.has_value() && data_ptr == nullptr), "allocator failed in function"); if( data_ptr != nullptr and rhs.data_ptr != nullptr ) this->condestructor(data_size <= sizeof(data_ptr) ? (char*)&data_ptr : data_ptr, rhs.data_ptr); return *this; } ret operator()(args&&... argv) { return invokator(data_ptr, gp::forward(argv)...); } }; /*template class function{ struct virtual_callable { virtual void inplace_move(char*) = 0; virtual virtual_callable* all_copy() = 0; virtual void inplace_copy(char*) = 0; virtual virtual_callable* all_move() = 0; virtual ~virtual_callable() = default; virtual ret operator() (args...) = 0; }; template class callable final : public virtual_callable{ typename gp::remove_reference::type internal_representation; public: callable(const fn func) : internal_representation{gp::move(func)} {} callable(callable&) = default; callable(callable&&) = default; virtual ~callable() override = default; virtual void inplace_copy(char* ptr) override { new(ptr) callable(*this); } virtual virtual_callable* all_copy() override { return new callable(*this); } virtual void inplace_move(char* ptr) override { new(ptr) callable(gp::move(*this)); } virtual virtual_callable* all_move() override { return new callable(gp::move(*this)); } ret operator() (args... arg_list) override { return internal_representation(arg_list...); } }; // tweak a way to store a size in there for trivial copy enum state_t : uint8_t{ INACTIVE = 0, ACTIVE = 1, NO_SOO = 0, SOO = 2 }; state_t state{}; union{ virtual_callable* functor = nullptr; char inplace[16]; } self; public: template function& operator=(T& t) { if(state & ACTIVE) { if(state & SOO) { ((virtual_callable*)self.inplace)->~virtual_callable(); } else { delete self.functor; } } if(!(t.state & ACTIVE)) { state = INACTIVE; return; } if constexpr (!std::is_same_v>) { if constexpr (sizeof(callable) <= sizeof(self)) { new((void*)self.inplace) callable(t); state = (state_t)(ACTIVE | SOO); } else { self.functor = new callable(t); state = (state_t)(ACTIVE | NO_SOO); } } else { if(t.state & SOO) { auto& ref = t.self.functor; ref->inplace_copy((char*)&self); state = (state_t)(ACTIVE | SOO); } else { self.functor = t.self.functor->all_copy(); state = (state_t)(ACTIVE | NO_SOO); } } } function() { state = INACTIVE; } template function<>(T& t) { if constexpr (!std::is_same_v>) { if constexpr (sizeof(callable) <= sizeof(self)) { new((void*)self.inplace) callable(t); state = (state_t)(ACTIVE | SOO); } else { self.functor = new callable(t); state = (state_t)(ACTIVE | NO_SOO); } } else { if(t.state & SOO) { t.self.functor->inplace_copy(self.inplace); state = (state_t)(ACTIVE | SOO); } else { self.functor = t.self.functor->all_copy(); state = (state_t)(ACTIVE | NO_SOO); } } } template function(T&& t) { if constexpr (!std::is_same_v>) { if constexpr (sizeof(callable) <= sizeof(self)) { new((void*)self.inplace) callable(gp::move(t)); state = (state_t)(ACTIVE | SOO); } else { self.functor = new callable(gp::move(t)); state = (state_t)(ACTIVE | NO_SOO); } } else { if(t.state & SOO) { auto& ref = t.self.functor; ref->inplace_move((char*)&self); state = (state_t)(ACTIVE | SOO); } else { self.functor = t.self.functor->all_move(); state = (state_t)(ACTIVE | NO_SOO); } } } ret operator()(args... arg_list) const { if constexpr (gp_config::has_exceptions) { if(!(state & ACTIVE)) { throw bad_functor{}; } } if(state & SOO) { return (*(virtual_callable*)&self)(arg_list...); } else { return (*self.functor)(arg_list...); } } ~function() { if(state & ACTIVE) { if(state & SOO) { ((virtual_callable*)&self)->~virtual_callable(); } else { delete self.functor; } } } };*/ }