From 462e6f0659a0e96904088e2641d9fc9c1daca635 Mon Sep 17 00:00:00 2001 From: q66 Date: Sun, 11 Sep 2016 17:58:16 +0200 Subject: [PATCH] new ostd::Function implementation (cleaner with better allocator awareness including stateful allocators) --- ostd/functional.hh | 752 +++++++++++++++++++++++---------------------- ostd/memory.hh | 4 +- 2 files changed, 393 insertions(+), 363 deletions(-) diff --git a/ostd/functional.hh b/ostd/functional.hh index 4594267..aa731e2 100644 --- a/ostd/functional.hh +++ b/ostd/functional.hh @@ -1,6 +1,7 @@ /* Function objects for OctaSTD. * * This file is part of OctaSTD. See COPYING.md for futher information. + * Portions of this file are originally adapted from the libc++ project. */ #ifndef OSTD_FUNCTIONAL_HH @@ -13,6 +14,7 @@ #include "ostd/memory.hh" #include "ostd/utility.hh" #include "ostd/type_traits.hh" +#include namespace ostd { @@ -542,409 +544,435 @@ detail::MemFn mem_fn(R T:: *ptr) { return detail::MemFn(ptr); } -/* function impl - * reference: http://probablydance.com/2013/01/13/a-faster-implementation-of-stdfunction +/* function impl adapted from libc++ */ template -struct Function; +class Function; namespace detail { - struct FunctorData { - void *p1, *p2; + template + inline bool func_is_not_null(T const &) { + return true; + } + + template + inline bool func_is_not_null(T *ptr) { + return ptr; + } + + template + inline bool func_is_not_null(R C::*ptr) { + return ptr; + } + + template + inline bool func_is_not_null(Function const &f) { + return !!f; + } + + template + inline auto func_invoke_helper(F &&f, A &&...args) { + return forward(f)(forward(args)...); + } + + template + struct FuncInvokeVoidReturnWrapper { + template + static R call(A &&...args) { + return func_invoke_helper(forward(args)...); + } + }; + + template<> + struct FuncInvokeVoidReturnWrapper { + template + static void call(A &&...args) { + func_invoke_helper(forward(args)...); + } }; template - constexpr bool FunctorInPlace = - sizeof(T) <= sizeof(FunctorData) && - (alignof(FunctorData) % alignof(T)) == 0 && IsMoveConstructible; - - struct FunctionManager; - - struct FmStorage { - FunctorData data; - FunctionManager const *manager; - - template - A &get_alloc() { - union { - FunctionManager const **m; - A *alloc; - } u; - u.m = &manager; - return *u.alloc; - } - template - A const &get_alloc() const { - union { - FunctionManager const * const *m; - A const *alloc; - } u; - u.m = &manager; - return *u.alloc; - } - }; - - template - struct FunctorDataManager { - template - static R call(FunctorData const &s, Args ...args) { - return (*reinterpret_cast(&const_cast(s)))( - forward(args)... - ); - } - - static void store_f(FmStorage &s, T v) { - new (&get_ref(s)) T(forward(v)); - } - - static void move_f(FmStorage &lhs, FmStorage &&rhs) { - new (&get_ref(lhs)) T(move(get_ref(rhs))); - } - - static void destroy_f(A &, FmStorage &s) { - get_ref(s).~T(); - } - - static T &get_ref(FmStorage const &s) { - union { - FunctorData const *data; - T *ret; - } u; - u.data = &s.data; - return *u.ret; - } - }; - - template - struct FunctorDataManager>> { - template - static R call(FunctorData const &s, Args ...args) { - return (*reinterpret_cast &>( - const_cast(s)) - )(forward(args)...); - } - - static void store_f(FmStorage &s, T v) { - A &a = s.get_alloc(); - AllocatorPointer *ptr = new (&get_ptr_ref(s)) - AllocatorPointer(allocator_allocate(a, 1)); - allocator_construct(a, *ptr, forward(v)); - } - - static void move_f(FmStorage &lhs, FmStorage &&rhs) { - new (&get_ptr_ref(lhs)) AllocatorPointer(move(get_ptr_ref(rhs))); - get_ptr_ref(rhs) = nullptr; - } - - static void destroy_f(A &a, FmStorage &s) { - AllocatorPointer &ptr = get_ptr_ref(s); - if (!ptr) { - return; - } - allocator_destroy(a, ptr); - allocator_deallocate(a, ptr, 1); - ptr = nullptr; - } - - static T &get_ref(FmStorage const &s) { - return *get_ptr_ref(s); - } - - static AllocatorPointer &get_ptr_ref(FmStorage &s) { - return reinterpret_cast &>(s.data); - } - - static AllocatorPointer &get_ptr_ref(FmStorage const &s) { - return reinterpret_cast &>( - const_cast(s.data) - ); - } - }; - - template - static FunctionManager const &get_default_fm(); - - template - static void create_fm(FmStorage &s, A &&a) { - new (&s.get_alloc()) A(move(a)); - s.manager = &get_default_fm(); - } - - struct FunctionManager { - template - inline static constexpr FunctionManager create_default_manager() { - return FunctionManager { - &call_move_and_destroy, - &call_copy, - &call_copy_fo, - &call_destroy - }; - } - - void (* const call_move_and_destroyf)( - FmStorage &lhs, FmStorage &&rhs - ); - void (* const call_copyf)( - FmStorage &lhs, FmStorage const &rhs - ); - void (* const call_copyf_fo)( - FmStorage &lhs, FmStorage const &rhs - ); - void (* const call_destroyf)(FmStorage &s); - - template - static void call_move_and_destroy(FmStorage &lhs, FmStorage &&rhs) { - using Spec = FunctorDataManager; - Spec::move_f(lhs, move(rhs)); - Spec::destroy_f(rhs.get_alloc(), rhs); - create_fm(lhs, move(rhs.get_alloc())); - rhs.get_alloc().~A(); - } - - template - static void call_copy(FmStorage &lhs, FmStorage const &rhs) { - using Spec = FunctorDataManager; - create_fm(lhs, A(rhs.get_alloc())); - Spec::store_f(lhs, Spec::get_ref(rhs)); - } - - template - static void call_copy_fo(FmStorage &lhs, FmStorage const &rhs) { - using Spec = FunctorDataManager; - Spec::store_f(lhs, Spec::get_ref(rhs)); - } - - template - static void call_destroy(FmStorage &s) { - using Spec = FunctorDataManager; - Spec::destroy_f(s.get_alloc(), s); - s.get_alloc().~A(); - } - }; - - template - inline static FunctionManager const &get_default_fm() { - static FunctionManager const def_manager = - FunctionManager::create_default_manager(); - return def_manager; - } - - template - struct FunctionBase { - using Result = R; - }; - - template - struct FunctionBase { - using Result = R; - using Argument = T; - }; - - template - struct FunctionBase { - using Result = R; - using FirstArgument = T; - using SecondArgument = U; - }; - - template - constexpr bool IsValidFunctor = false; + class FuncBase; template - constexpr bool IsValidFunctor, R(A...)> = false; + class FuncBase { + FuncBase(FuncBase const &); + FuncBase &operator=(FuncBase const &); + public: + FuncBase() {} + virtual ~FuncBase() {} + virtual FuncBase *clone() const = 0; + virtual void clone(FuncBase *) const = 0; + virtual void destroy() noexcept = 0; + virtual void destroy_deallocate() noexcept = 0; + virtual R operator()(A &&...args) = 0; + }; - template - T func_to_functor(T &&f) { - return forward(f); + template + class FuncCore; + + template + class FuncCore: public FuncBase { + CompressedPair f_stor; +public: + explicit FuncCore(F &&f): + f_stor( + piecewise_construct, + forward_as_tuple(ostd::move(f)), + forward_as_tuple() + ) + {} + + explicit FuncCore(F const &f, A const &a): + f_stor( + piecewise_construct, + forward_as_tuple(f), + forward_as_tuple(a) + ) + {} + + explicit FuncCore(F const &f, A &&a): + f_stor( + piecewise_construct, + forward_as_tuple(f), + forward_as_tuple(ostd::move(a)) + ) + {} + + explicit FuncCore(F &&f, A &&a): + f_stor( + piecewise_construct, + forward_as_tuple(ostd::move(f)), + forward_as_tuple(ostd::move(a)) + ) + {} + + virtual FuncBase *clone() const; + virtual void clone(FuncBase *) const; + virtual void destroy() noexcept; + virtual void destroy_deallocate() noexcept; + virtual R operator()(AT &&...args); + }; + + template + FuncBase *FuncCore::clone() const { + using AA = AllocatorRebind; + AA a(f_stor.second()); + using D = AllocatorDestructor; + Box hold(a.allocate(1), D(a, 1)); + ::new(hold.get()) FuncCore(f_stor.first(), A(a)); + return hold.release(); } - template - auto func_to_functor(RR (T::*f)(AA...)) - -> decltype(mem_fn(f)) { - return mem_fn(f); + template + void FuncCore::clone(FuncBase *p) const { + ::new (p) FuncCore(f_stor.first(), f_stor.second()); } - template - auto func_to_functor(RR (T::*f)(AA...) const) - -> decltype(mem_fn(f)) { - return mem_fn(f); + template + void FuncCore::destroy() noexcept { + f_stor.~CompressedPair(); } - struct ValidFunctorNat {}; + template + void FuncCore::destroy_deallocate() noexcept { + using AA = AllocatorRebind; + AA a(f_stor.second()); + f_stor.~CompressedPair(); + a.deallocate(this, 1); + } - template - static decltype( - func_to_functor(declval()) (declval()...) - ) valid_functor_test(U *); - template - static ValidFunctorNat valid_functor_test(...); - - template - constexpr bool IsValidFunctor = - IsConvertible(nullptr)), R>; - - template - using FunctorType = decltype(func_to_functor(declval())); + template + R FuncCore::operator()(AT &&...args) { + using Invoker = FuncInvokeVoidReturnWrapper; + return Invoker::call(f_stor.first(), forward(args)...); + } } /* namespace detail */ template -struct Function: detail::FunctionBase { - Function( ) { init_empty(); } - Function(Nullptr) { init_empty(); } +class Function { + using Base = detail::FuncBase; + AlignedStorage<3 * sizeof(void *)> p_buf; + Base *p_f; - Function(Function &&f) { - init_empty(); - swap(f); + static inline Base *as_base(void *p) { + return reinterpret_cast(p); } - Function(Function const &f): p_call(f.p_call) { - f.p_stor.manager->call_copyf(p_stor, f.p_stor); - } + template< + typename F, + bool = !IsSame && detail::IsInvokable + > + struct CallableBase; - template - >> - Function(T f) { - if (func_is_null(f)) { - init_empty(); - return; - } - initialize(detail::func_to_functor( - forward(f)), Allocator>() - ); - } - - template - Function(AllocatorArg, A const &) { init_empty(); } - - template - Function(AllocatorArg, A const &, Nullptr) { init_empty(); } - - template - Function(AllocatorArg, A const &, Function &&f) { - init_empty(); - swap(f); - } - - template - Function(AllocatorArg, A const &a, Function const &f): - p_call(f.p_call) - { - detail::FunctionManager const *mfa = - &detail::get_default_fm, A>(); - if (f.p_stor.manager == mfa) { - detail::create_fm, A>(p_stor, A(a)); - mfa->call_copyf_fo(p_stor, f.p_stor); - return; - } - - using AA = AllocatorRebind; - detail::FunctionManager const *mff = - &detail::get_default_fm(); - if (f.p_stor.manager == mff) { - detail::create_fm(p_stor, AA(a)); - mff->call_copyf_fo(p_stor, f.P_stor); - return; - } - - initialize(f, AA(a)); - } - - template - >> - Function(AllocatorArg, A const &a, T f) { - if (func_is_null(f)) { - init_empty(); - return; - } - initialize(detail::func_to_functor(forward(f)), A(a)); - } - - ~Function() { - p_stor.manager->call_destroyf(p_stor); - } - - Function &operator=(Function &&f) { - p_stor.manager->call_destroyf(p_stor); - swap(f); - return *this; - } - - Function &operator=(Function const &f) { - p_stor.manager->call_destroyf(p_stor); - swap(Function(f)); - return *this; + template + struct CallableBase { + static constexpr bool value = + IsSame || IsConvertible, R>; + }; + template + struct CallableBase { + static constexpr bool value = false; }; - R operator()(Args ...args) const { - return p_call(p_stor.data, forward(args)...); + template + static constexpr bool Callable = CallableBase::value; + +public: + using Result = R; + + Function() noexcept: p_f(nullptr) {} + Function(Nullptr) noexcept: p_f(nullptr) {} + + Function(Function const &); + Function(Function &&) noexcept; + + template< + typename F, typename = EnableIf && !IsSame> + > + Function(F); + + template + Function(AllocatorArg, A const &) noexcept: p_f(nullptr) {} + template + Function(AllocatorArg, A const &, Nullptr) noexcept: p_f(nullptr) {} + template + Function(AllocatorArg, A const &, Function const &); + template + Function(AllocatorArg, A const &, Function &&); + template>> + Function(AllocatorArg, A const &, F); + + Function &operator=(Function const &f) { + Function(f).swap(*this); + return *this; } - template - void assign(F &&f, A const &a) { - Function(allocator_arg, a, forward(f)).swap(*this); + Function &operator=(Function &&) noexcept; + Function &operator=(Nullptr) noexcept; + + template + EnableIf< + Callable> && !IsSame, Function>, + Function & + > operator=(F &&f) { + Function(forward(f)).swap(*this); + return *this; } - void swap(Function &f) { - detail::FmStorage tmp; - f.p_stor.manager->call_move_and_destroyf(tmp, move(f.p_stor)); - p_stor.manager->call_move_and_destroyf(f.p_stor, move(p_stor)); - tmp.manager->call_move_and_destroyf(p_stor, move(tmp)); - ostd::swap(p_call, f.p_call); - } + ~Function(); - explicit operator bool() const { return p_call != nullptr; } + void swap(Function &) noexcept; -private: - detail::FmStorage p_stor; - R (*p_call)(detail::FunctorData const &, Args...); + explicit operator bool() const noexcept { return p_f; } - template - void initialize(T &&f, A &&a) { - p_call = &detail::FunctorDataManager::template call; - detail::create_fm(p_stor, forward(a)); - detail::FunctorDataManager::store_f(p_stor, forward(f)); - } + /* deleted overloads close possible hole in the type system */ + template + bool operator==(Function &) const = delete; + template + bool operator!=(Function &) const = delete; - void init_empty() { - using emptyf = R(*)(Args...); - using emptya = Allocator; - p_call = nullptr; - detail::create_fm(p_stor, emptya()); - detail::FunctorDataManager::store_f(p_stor, nullptr); - } - - template - static bool func_is_null(T const &) { return false; } - - static bool func_is_null(R (* const &fptr)(Args...)) { - return fptr == nullptr; - } - - template - static bool func_is_null(RR (T::* const &fptr)(AArgs...)) { - return fptr == nullptr; - } - - template - static bool func_is_null(RR (T::* const &fptr)(AArgs...) const) { - return fptr == nullptr; + R operator()(Args ...a) const { + return (*p_f)(forward(a)...); } }; -template -bool operator==(Nullptr, Function const &rhs) { return !rhs; } +template +Function::Function(Function const &f) { + if (!f.p_f) { + p_f = nullptr; + } else if (static_cast(f.p_f) == &f.p_buf) { + p_f = as_base(&p_buf); + f.p_f->clone(p_f); + } else { + p_f = f.p_f->clone(); + } +} -template -bool operator==(Function const &lhs, Nullptr) { return !lhs; } +template +template +Function::Function( + AllocatorArg, A const &, Function const &f +) { + if (!f.p_f) { + p_f = nullptr; + } else if (static_cast(f.p_f) == &f.p_buf) { + p_f = as_base(&p_buf); + f.p_f->clone(p_f); + } else { + p_f = f.p_f->clone(); + } +} -template -bool operator!=(Nullptr, Function const &rhs) { return rhs; } +template +Function::Function(Function &&f) noexcept { + if (!f.p_f) { + p_f = nullptr; + } else if (static_cast(f.p_f) == &f.p_buf) { + p_f = as_base(&p_buf); + f.p_f->clone(p_f); + } else { + p_f = f.p_f; + f.p_f = nullptr; + } +} -template -bool operator!=(Function const &lhs, Nullptr) { return lhs; } +template +template +Function::Function( + AllocatorArg, A const &, Function &&f +) { + if (!f.p_f) { + p_f = nullptr; + } else if (static_cast(f.p_f) == &f.p_buf) { + p_f = as_base(&p_buf); + f.p_f->clone(p_f); + } else { + p_f = f.p_f; + f.p_f = nullptr; + } +} + +template +template +Function::Function(F f): p_f(nullptr) { + if (!detail::func_is_not_null(f)) { + return; + } + using FF = detail::FuncCore, R(Args...)>; + if ((sizeof(FF) <= sizeof(p_buf)) && IsNothrowCopyConstructible) { + p_f = ::new(static_cast(&p_buf)) FF(move(f)); + return; + } + using AA = Allocator; + AA a; + using D = detail::AllocatorDestructor; + Box hold(a.allocate(1), D(a, 1)); + ::new(hold.get()) FF(move(f), Allocator(a)); + p_f = hold.release(); +} + +template +template +Function::Function(AllocatorArg, A const &a, F f): + p_f(nullptr) +{ + if (!detail::func_is_not_null(f)) { + return; + } + using FF = detail::FuncCore; + using AA = AllocatorRebind; + AA aa(a); + if ( + (sizeof(FF) <= sizeof(p_buf)) && IsNothrowCopyConstructible && + IsNothrowCopyConstructible + ) { + p_f = ::new(static_cast(&p_buf)) FF(move(f), A(aa)); + return; + } + using D = detail::AllocatorDestructor; + Box hold(aa.allocate(1), D(aa, 1)); + ::new(hold.get()) FF(move(f), A(aa)); + p_f = hold.release(); +} + +template +Function &Function::operator=(Function &&f) + noexcept +{ + if (static_cast(p_f) == &p_buf) { + p_f->destroy(); + } else if (p_f) { + p_f->destroy_deallocate(); + } + if (f.p_f == nullptr) { + p_f = nullptr; + } else if (static_cast(f.p_f) == &f.p_buf) { + p_f = as_base(&p_buf); + f.p_f->clone(p_f); + } else { + p_f = f.p_f; + f.p_f = nullptr; + } + return *this; +} + +template +Function &Function::operator=(Nullptr) noexcept { + if (static_cast(p_f) == &p_buf) { + p_f->destroy(); + } else if (p_f) { + p_f->destroy_deallocate(); + } + p_f = nullptr; + return *this; +} + +template +Function::~Function() { + if (static_cast(p_f) == &p_buf) { + p_f->destroy(); + } else if (p_f) { + p_f->destroy_deallocate(); + } +} + +template +void Function::swap(Function &f) noexcept { + if ( + (static_cast(p_f) == &p_buf) && + (static_cast(f.p_f) == &f.p_buf) + ) { + /* both in small storage */ + AlignedStorage tmpbuf; + Base *t = as_base(&tmpbuf); + p_f->clone(t); + p_f->destroy(); + p_f = nullptr; + f.p_f->clone(as_base(&p_buf)); + f.p_f->destroy(); + f.p_f = nullptr; + p_f = as_base(&p_buf); + t->clone(as_base(&f.p_buf)); + t->destroy(); + f.p_f = as_base(&f.p_buf); + } else if (static_cast(p_f) == &p_buf) { + /* ours in small storage */ + p_f->clone(as_base(&f.p_buf)); + p_f->destroy(); + p_f = f.p_f; + f.p_f = as_base(&f.p_buf); + } else if (static_cast(f.p_f) == &f.p_buf) { + /* the other in small storage */ + f.p_f->clone(as_base(&p_buf)); + f.p_f->destroy(); + f.p_f = p_f; + p_f = as_base(&p_buf); + } else { + detail::swap_adl(p_f, f.p_f); + } +} + +template +inline bool operator==(Function const &f, Nullptr) noexcept { + return !f; +} + +template +inline bool operator==(Nullptr, Function const &f) noexcept { + return !f; +} + +template +inline bool operator!=(Function const &f, Nullptr) noexcept { + return bool(f); +} + +template +inline bool operator!=(Nullptr, Function const &f) noexcept { + return bool(f); +} namespace detail { template diff --git a/ostd/memory.hh b/ostd/memory.hh index b8a59be..5f7aac4 100644 --- a/ostd/memory.hh +++ b/ostd/memory.hh @@ -1157,8 +1157,10 @@ namespace detail { A &p_alloc; Size p_size; public: + using Pointer = AllocatorPointer; + using Size = ostd::Size; AllocatorDestructor(A &a, Size s) noexcept: p_alloc(a), p_size(s) {} - void operator()(AllocatorPointer p) noexcept { + void operator()(Pointer p) noexcept { allocator_deallocate(p_alloc, p, p_size); } };