#pragma once
|
|
#include "gp/exception.hpp"
|
|
#include "gp/algorithm/tmp_manip.hpp"
|
|
#include "gp/algorithm/move.hpp"
|
|
#include "gp/algorithm/modifiers.hpp"
|
|
|
|
namespace gp{
|
|
template <typename fn, typename allocator, bool copy_allocator = false>
|
|
class function;
|
|
|
|
template <typename ret, typename allocator, bool copy_allocator, typename ...args>
|
|
class function<ret(args...), allocator, copy_allocator>{
|
|
using fn_ptr = char*;
|
|
|
|
using invoke_fn_t = ret (*)(fn_ptr, args&&...);
|
|
using condestruct_fn_t = void (*) (fn_ptr, fn_ptr);
|
|
using allocator_t = typename gp::either<
|
|
copy_allocator,
|
|
allocator,
|
|
gp::reference_wrapper<allocator>
|
|
>::type;
|
|
|
|
allocator_t 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:
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Wnull-dereference"
|
|
#pragma gcc diagnostic push
|
|
#pragma gcc diagnostic ignored "-Wnull-dereference"
|
|
function(allocator_t alloc_v = gp::reference_wrapper<allocator_t>{*reinterpret_cast<allocator_t*>(0)})
|
|
#pragma gcc pop
|
|
#pragma clang pop
|
|
: alloc(alloc_v)
|
|
, invokator(nullptr)
|
|
, condestructor(nullptr)
|
|
, data_ptr(nullptr)
|
|
, data_size(0)
|
|
{}
|
|
|
|
template<typename func>
|
|
function(func f, allocator_t alloc_v)
|
|
: alloc(alloc_v)
|
|
, invokator(reinterpret_cast<invoke_fn_t>(invoke<func>))
|
|
, condestructor(reinterpret_cast<condestruct_fn_t>(condestruct<func>))
|
|
, data_ptr((char*)((allocator&)alloc).allocate(sizeof(func)))
|
|
, data_size(sizeof(func))
|
|
{
|
|
gp_config::assertion(data_ptr != nullptr, "allocator failed in function");
|
|
if(data_ptr != nullptr) this->condestructor(data_ptr, reinterpret_cast<char*>(&f));
|
|
}
|
|
|
|
function(function const& rhs)
|
|
: alloc(rhs.alloc)
|
|
, invokator(rhs.invokator)
|
|
, condestructor(rhs.condestructor)
|
|
, data_ptr(rhs.data_size != 0 ? (char*)((allocator&)alloc).allocate(rhs.data_size) : nullptr)
|
|
, data_size(rhs.data_size)
|
|
{
|
|
gp_config::assertion(data_ptr != nullptr, "allocator failed in function");
|
|
if(
|
|
data_ptr != nullptr
|
|
and rhs.data_ptr != nullptr
|
|
) this->condestructor(data_ptr, rhs.data_ptr);
|
|
}
|
|
|
|
function(function&& rhs)
|
|
: alloc(rhs.alloc)
|
|
, invokator(rhs.invokator)
|
|
, condestructor(rhs.condestructor)
|
|
, data_ptr(rhs.data_ptr)
|
|
, data_size(rhs.data_size)
|
|
{
|
|
rhs.data_ptr = nullptr;
|
|
}
|
|
|
|
constexpr function(ret(*fn)(args...), allocator_t alloc_v = allocator_t{})
|
|
: alloc(alloc_v)
|
|
, invokator(reinterpret_cast<invoke_fn_t>(invoke<ret(args...)>))
|
|
, condestructor(nop_condestruct)
|
|
, data_ptr(reinterpret_cast<char*>(fn))
|
|
, data_size(0)
|
|
{}
|
|
|
|
~function(){
|
|
if(data_size == 0) {
|
|
return;
|
|
}
|
|
if(data_ptr != nullptr) {
|
|
condestructor(nullptr, data_ptr);
|
|
((allocator&)alloc).deallocate(data_ptr);
|
|
data_ptr = nullptr;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
ret operator()(args&&... argv) {
|
|
return invokator(data_ptr, gp::forward<args>(argv)...);
|
|
}
|
|
};
|
|
|
|
/*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;
|
|
}
|
|
}
|
|
|
|
}
|
|
};*/
|
|
|
|
}
|