#pragma once
|
|
|
|
#include "gp/exception.hpp"
|
|
#include "gp/algorithms/modifiers.hpp"
|
|
#include "gp/algorithms/move.hpp"
|
|
#include "gp/algorithms/tmp_manip.hpp"
|
|
#include "gp/utils/allocators/allocator.hpp"
|
|
#include "gp/functional/optional.hpp"
|
|
|
|
namespace gp{
|
|
|
|
namespace details {
|
|
template<typename pred, typename returner>
|
|
auto ensure(pred a, returner b) {
|
|
gp_config::assertion(a(), "could not ensure predicate");
|
|
return b();
|
|
}
|
|
}
|
|
|
|
template <typename fn>
|
|
class function;
|
|
|
|
template <typename ret, typename ...args>
|
|
class function<ret(args...)>{
|
|
using fn_ptr = char*;
|
|
|
|
using invoke_fn_t = ret (*)(fn_ptr, args&&...);
|
|
using condestruct_fn_t = void (*) (fn_ptr, fn_ptr);
|
|
|
|
gp::optional<gp::reference_wrapper<allocator>> alloc;
|
|
invoke_fn_t invokator;
|
|
condestruct_fn_t condestructor;
|
|
fn_ptr data_ptr;
|
|
size_t data_size;
|
|
|
|
|
|
template<typename func>
|
|
static ret invoke(func* fn, args&&... fw_args) {
|
|
return (*fn)(gp::forward<args>(fw_args)...);
|
|
}
|
|
|
|
template<typename func>
|
|
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:
|
|
function(allocator& alloc_v)
|
|
: alloc(alloc_v)
|
|
, invokator(nullptr)
|
|
, condestructor(nullptr)
|
|
, data_ptr(nullptr)
|
|
, data_size(0)
|
|
{}
|
|
|
|
template<typename func>
|
|
function(func f, allocator& alloc_v)
|
|
: alloc(alloc_v)
|
|
, invokator(reinterpret_cast<invoke_fn_t>(invoke<func>))
|
|
, condestructor(reinterpret_cast<condestruct_fn_t>(condestruct<func>))
|
|
, data_ptr(
|
|
sizeof(func) <= sizeof(data_ptr)
|
|
? nullptr
|
|
: details::ensure(
|
|
[&](){return alloc.has_value();},
|
|
[&](){return (char*)(alloc.value().get().allocate(sizeof(func)));}
|
|
)
|
|
)
|
|
, data_size(
|
|
sizeof(func)
|
|
)
|
|
{
|
|
gp_config::assertion(data_size <= sizeof(func) || !(alloc.has_value() && data_ptr == nullptr), "allocator failed in function");
|
|
this->condestructor(data_size <= sizeof(data_ptr) ? (char*)&data_ptr : data_ptr, reinterpret_cast<char*>(&f));
|
|
}
|
|
|
|
template<typename func>
|
|
function(func f, nullopt_t alloc_v)
|
|
: alloc(alloc_v)
|
|
, invokator(reinterpret_cast<invoke_fn_t>(invoke<func>))
|
|
, condestructor(reinterpret_cast<condestruct_fn_t>(condestruct<func>))
|
|
, data_ptr()
|
|
, data_size(
|
|
details::ensure(
|
|
[&](){return sizeof(func) <= sizeof(data_ptr);},
|
|
[&](){return sizeof(func);}
|
|
)
|
|
)
|
|
{
|
|
this->condestructor((char*)&data_ptr, reinterpret_cast<char*>(&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(data_size <= sizeof(data_ptr) || !(alloc.has_value() && data_ptr == nullptr), "allocator failed in function");
|
|
if(
|
|
data_size != 0
|
|
and rhs.data_size != 0
|
|
) this->condestructor(
|
|
data_size <= sizeof(data_ptr) ? (char*)&data_ptr : data_ptr,
|
|
data_size <= sizeof(data_ptr) ? (char*)&(rhs.data_ptr) : rhs.data_ptr
|
|
);
|
|
}
|
|
|
|
function(function&& rhs)
|
|
: alloc(rhs.alloc)
|
|
, invokator(rhs.invokator)
|
|
, condestructor(rhs.condestructor)
|
|
, data_ptr(rhs.data_size <= sizeof(data_ptr) ? nullptr : rhs.data_ptr)
|
|
, data_size(rhs.data_size)
|
|
{
|
|
if(data_size != 0 && data_size <= sizeof(data_ptr)) {
|
|
condestructor((char*)&data_ptr, (char*)&rhs.data_ptr);
|
|
condestructor(nullptr, (char*)&rhs.data_ptr);
|
|
}
|
|
rhs.data_ptr = nullptr;
|
|
rhs.data_size = 0;
|
|
}
|
|
|
|
~function(){
|
|
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;
|
|
|
|
/* Cleanup */
|
|
|
|
if(data_size > sizeof(data_ptr)) {
|
|
condestructor(nullptr, data_ptr);
|
|
alloc.value().get().deallocate(data_ptr);
|
|
} else if(data_size) {
|
|
condestructor(nullptr, (char*)&data_ptr);
|
|
}
|
|
|
|
/* Reallocation */
|
|
|
|
data_size = rhs.data_size;
|
|
data_ptr = data_size <= sizeof(data_ptr)
|
|
? 0
|
|
: details::ensure(
|
|
[&](){return alloc.has_value();},
|
|
[&](){return (char*)(alloc.value().get().allocate(rhs.data_size));}
|
|
);
|
|
|
|
gp_config::assertion(!(alloc.has_value() && data_size > 8 && data_ptr != nullptr), "allocator failed in function");
|
|
|
|
if(
|
|
data_size > 0
|
|
) this->condestructor(
|
|
data_size <= sizeof(data_ptr) ? (char*)&data_ptr : data_ptr,
|
|
data_size <= sizeof(data_ptr) ? (char*)&rhs.data_ptr : rhs.data_ptr
|
|
);
|
|
return *this;
|
|
}
|
|
|
|
ret operator()(args&&... argv) {
|
|
return invokator(data_size <= sizeof(data_ptr) ? (fn_ptr)&data_ptr : data_ptr, gp::forward<args>(argv)...);
|
|
}
|
|
|
|
bool ready() {
|
|
return invokator != nullptr;
|
|
}
|
|
};
|
|
|
|
/*template <typename ret, typename ...args>
|
|
class function<ret(args...)>{
|
|
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<typename fn>
|
|
class callable final : public virtual_callable{
|
|
typename gp::remove_reference<fn>::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 <typename T>
|
|
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<T, function<ret(args...)>>) {
|
|
if constexpr (sizeof(callable<T>) <= sizeof(self))
|
|
{
|
|
new((void*)self.inplace) callable<T>(t);
|
|
state = (state_t)(ACTIVE | SOO);
|
|
}
|
|
else
|
|
{
|
|
self.functor = new callable<T>(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<typename T>
|
|
function<>(T& t)
|
|
{
|
|
if constexpr (!std::is_same_v<T, function<ret(args...)>>) {
|
|
if constexpr (sizeof(callable<T>) <= sizeof(self))
|
|
{
|
|
new((void*)self.inplace) callable<T>(t);
|
|
state = (state_t)(ACTIVE | SOO);
|
|
}
|
|
else
|
|
{
|
|
self.functor = new callable<T>(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 <typename T>
|
|
function(T&& t)
|
|
{
|
|
if constexpr (!std::is_same_v<T, function<ret(args...)>>) {
|
|
if constexpr (sizeof(callable<T>) <= sizeof(self))
|
|
{
|
|
new((void*)self.inplace) callable<T>(gp::move(t));
|
|
state = (state_t)(ACTIVE | SOO);
|
|
}
|
|
else
|
|
{
|
|
self.functor = new callable<T>(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;
|
|
}
|
|
}
|
|
|
|
}
|
|
};*/
|
|
|
|
}
|