From b23b6e0f5cf469a5dcce84e3854c8c9209a6e4c1 Mon Sep 17 00:00:00 2001 From: syntheticmagus <33846034+syntheticmagus@users.noreply.github.com> Date: Mon, 14 Mar 2022 16:02:14 -0700 Subject: [PATCH 1/2] First working version with near-full duplication. --- .../arcana/functional/inplace_function.h | 360 ++++++++++++++++-- Source/Shared/arcana/threading/dispatcher.h | 2 +- 2 files changed, 329 insertions(+), 33 deletions(-) diff --git a/Source/Shared/arcana/functional/inplace_function.h b/Source/Shared/arcana/functional/inplace_function.h index 6788623..8300988 100644 --- a/Source/Shared/arcana/functional/inplace_function.h +++ b/Source/Shared/arcana/functional/inplace_function.h @@ -35,8 +35,7 @@ namespace stdext { constexpr size_t InplaceFunctionDefaultCapacity = 32; - - template + struct inplace_function_operation { enum class operations_enum @@ -46,15 +45,6 @@ namespace stdext Move }; }; - template<> - struct inplace_function_operation - { - enum class operations_enum - { - Destroy, - Move - }; - }; template class /*alignas(Alignment)*/ inplace_function; - template - class /*alignas(Alignment)*/ inplace_function + template + class /*alignas(Alignment)*/ inplace_function { public: template @@ -89,8 +79,6 @@ namespace stdext template inplace_function(const CallableT& c) { - static_assert(!Copyable || std::is_copy_constructible::value, - "Cannot create a copyable inplace function from a non-copyable callable."); this->set(c); } @@ -100,8 +88,6 @@ namespace stdext template::value>::type> inplace_function(CallableT&& c) { - static_assert(!Copyable || std::is_copy_constructible::value, - "Cannot create a copyable inplace function from a non-copyable callable."); this->set(std::move(c)); } @@ -109,7 +95,6 @@ namespace stdext // May throw any exception encountered by the constructor when copying the target object inplace_function(const inplace_function& other) { - static_assert(Copyable, "Cannot copy-construct from a non-copyable inplace function."); this->copy(other); } @@ -142,7 +127,6 @@ namespace stdext // May throw any exception encountered by the assignment operator when copying the target object inplace_function& operator=(const inplace_function& other) { - static_assert(Copyable, "Cannot copy-assign from a non-copyable inplace function"); this->clear(); this->copy(other); return *this; @@ -273,6 +257,7 @@ namespace stdext { static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + static_assert(OtherCopyable, "Cannot move an uncopyable inplace_function into a copyable one"); if (other.m_ManagerFctPtr) other.m_ManagerFctPtr(data(), other.data(), Operation::Move); @@ -296,7 +281,7 @@ namespace stdext using CompatibleFunctionPointer = RetT (*)(ArgsT...); using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); - using Operation = typename inplace_function_operation::operations_enum; + using Operation = inplace_function_operation::operations_enum; using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); InvokeFctPtrType m_InvokeFctPtr; @@ -339,7 +324,7 @@ namespace stdext new (buffer) FunctorT(ftor); // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; + m_ManagerFctPtr = &manage_function::call; // generate entry call m_InvokeFctPtr = &invoke; @@ -360,7 +345,7 @@ namespace stdext new (buffer) FunctorT(std::move(ftor)); // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; + m_ManagerFctPtr = &manage_function::call; // generate entry call m_InvokeFctPtr = &invoke; @@ -373,7 +358,7 @@ namespace stdext return (*functor)(std::forward(args)...); } - template + template struct manage_function { static void call(void* dataPtr, const void* fromPtr, Operation op) @@ -381,6 +366,12 @@ namespace stdext FunctorT* thisFunctor = reinterpret_cast(dataPtr); switch (op) { + case Operation::Copy: + { + const FunctorT* source = (const FunctorT*)const_cast(fromPtr); + new (thisFunctor) FunctorT(*source); + break; + } case Operation::Destroy: { thisFunctor->~FunctorT(); @@ -399,21 +390,326 @@ namespace stdext } } }; + }; + + template + class /*alignas(Alignment)*/ inplace_function + { + public: + template + friend class inplace_function; + + // TODO create free operator overloads, to handle switched arguments + + // Creates and empty inplace_function + inplace_function() + : m_InvokeFctPtr(&DefaultFunction) + , m_ManagerFctPtr(nullptr) + {} + + // Destroys the inplace_function. If the stored callable is valid, it is destroyed also + ~inplace_function() + { + this->clear(); + } + + // Creates an implace function, copying the target of other within the internal buffer + // If the callable is larger than the internal buffer, a compile-time error is issued + // May throw any exception encountered by the constructor when copying the target object + template + inplace_function(const CallableT& c) + { + this->set(c); + } + + // Moves the target of an implace function, storing the callable within the internal buffer + // If the callable is larger than the internal buffer, a compile-time error is issued + // May throw any exception encountered by the constructor when moving the target object + template::value>::type> + inplace_function(CallableT&& c) + { + this->set(std::move(c)); + } + + // Copy construction is not possible from uncopyable inplace_functions. + inplace_function(const inplace_function& other) = delete; + + // Move construct an implace_function, moving the other’s target to this inplace_function’s internal buffer + // May throw any exception encountered by the constructor when moving the target object + inplace_function(inplace_function&& other) + { + this->move(std::move(other)); + } + + // Allows for copying from inplace_function object of the same type, but with a smaller buffer + // May throw any exception encountered by the constructor when copying the target object + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function(const inplace_function& other) + { + this->copy(other); + } + + // Allows for moving an inplace_function object of the same type, but with a smaller buffer + // May throw any exception encountered by the constructor when moving the target object + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function(inplace_function&& other) + { + this->move(std::move(other)); + } + + // Copy assignment is not possible from uncopyable inplace_functions + inplace_function& operator=(const inplace_function& other) = delete; + + // Assigns the other’s target by way of moving + // May throw any exception encountered by the assignment operator when moving the target object + inplace_function& operator=(inplace_function&& other) + { + this->clear(); + this->move(std::move(other)); + return *this; + } + + // Allows for copy assignment of an inplace_function object of the same type, but with a smaller buffer + // If the copy constructor of target object throws, this is left in uninitialized state + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function& operator=(const inplace_function& other) + { + this->clear(); + this->copy(other); + return *this; + } + + // Allows for move assignment of an inplace_function object of the same type, but with a smaller buffer + // If the move constructor of target object throws, this is left in uninitialized state + // If OtherCapacity is greater than Capacity, a compile-time error is issued + template + inplace_function& operator=(inplace_function&& other) + { + this->clear(); + this->move(std::move(other)); + return *this; + } + + // Assign a new target + // If the copy constructor of target object throws, this is left in uninitialized state + template::value>::type> + inplace_function& operator=(const CallableT& target) + { + this->clear(); + this->set(target); + return *this; + } + + // Assign a new target by way of moving + // If the move constructor of target object throws, this is left in uninitialized state + template + inplace_function& operator=(Callable&& target) + { + this->clear(); + this->set(std::move(target)); + return *this; + } + + // Compares this inplace function with a null pointer + // Empty functions compare equal, non-empty functions compare unequal + bool operator==(std::nullptr_t) + { + return !operator bool(); + } + + // Compares this inplace function with a null pointer + // Empty functions compare equal, non-empty functions compare unequal + bool operator!=(std::nullptr_t) + { + return operator bool(); + } + + // Converts to 'true' if assigned + explicit operator bool() const throw() + { + return m_InvokeFctPtr != &DefaultFunction; + } + + // Invokes the target + // Throws std::bad_function_call if not assigned + RetT operator()(ArgsT... args) const + { + return m_InvokeFctPtr(std::forward(args)..., data()); + } + + // Swaps two targets + void swap(inplace_function& other) + { + BufferType tempData; + this->move(m_Data, tempData); + other.move(other.m_Data, m_Data); + this->move(tempData, other.m_Data); + std::swap(m_InvokeFctPtr, other.m_InvokeFctPtr); + std::swap(m_ManagerFctPtr, other.m_ManagerFctPtr); + } + + private: + using BufferType = typename std::aligned_storage::type; + void clear() + { + m_InvokeFctPtr = &DefaultFunction; + if (m_ManagerFctPtr) + m_ManagerFctPtr(data(), nullptr, Operation::Destroy); + m_ManagerFctPtr = nullptr; + } + + template + void copy(const inplace_function& other) + { + static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); + static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + + if (other.m_ManagerFctPtr) + other.m_ManagerFctPtr(data(), other.data(), Operation::Copy); + + m_InvokeFctPtr = other.m_InvokeFctPtr; + m_ManagerFctPtr = other.m_ManagerFctPtr; + } + + void move(BufferType& from, BufferType& to) + { + if (m_ManagerFctPtr) + m_ManagerFctPtr(&from, &to, Operation::Move); + else + to = from; + } + + template + void move(inplace_function&& other) + { + static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); + static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + + if (other.m_ManagerFctPtr) + other.m_ManagerFctPtr(data(), other.data(), Operation::Move); + + m_InvokeFctPtr = other.m_InvokeFctPtr; + m_ManagerFctPtr = other.m_ManagerFctPtr; + + other.m_InvokeFctPtr = &DefaultFunction; + // don't reset the others management function + // because it still needs to destroy the lambda its holding. + } + + void* data() + { + return &m_Data; + } + const void* data() const + { + return &m_Data; + } + + using CompatibleFunctionPointer = RetT (*)(ArgsT...); + using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); + using Operation = inplace_function_operation::operations_enum; + using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); + + InvokeFctPtrType m_InvokeFctPtr; + ManagerFctPtrType m_ManagerFctPtr; + + BufferType m_Data; + + static RetT DefaultFunction(ArgsT..., const void*) + { + throw std::bad_function_call(); + } + + void set(std::nullptr_t) + { + m_ManagerFctPtr = nullptr; + m_InvokeFctPtr = &DefaultFunction; + } + + // For function pointers + void set(CompatibleFunctionPointer ptr) + { + // this is dodgy, and - according to standard - undefined behaviour. But it works + // see: http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type + m_ManagerFctPtr = nullptr; + m_InvokeFctPtr = reinterpret_cast(ptr); + } + + // Set - for functors + // enable_if makes sure this is excluded for function references and pointers. + template + typename std::enable_if::value && !std::is_function::value>::type + set(const FunctorArgT& ftor) + { + using FunctorT = typename std::remove_reference::type; + static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); + static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); + + // copy functor into the mem buffer + FunctorT* buffer = reinterpret_cast(&m_Data); + new (buffer) FunctorT(ftor); + + // generate destructor, copy-constructor and move-constructor + m_ManagerFctPtr = &manage_function::call; + + // generate entry call + m_InvokeFctPtr = &invoke; + } + + // Set - for functors + // enable_if makes sure this is excluded for function references and pointers. + template + typename std::enable_if::value && !std::is_function::value>::type + set(FunctorArgT&& ftor) + { + using FunctorT = typename std::remove_reference::type; + static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); + static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); + + // copy functor into the mem buffer + FunctorT* buffer = reinterpret_cast(&m_Data); + new (buffer) FunctorT(std::move(ftor)); + + // generate destructor and move-constructor + m_ManagerFctPtr = &manage_function::call; + + // generate entry call + m_InvokeFctPtr = &invoke; + } + + template + static RetT invoke(ArgsT... args, const void* dataPtr) + { + FunctorT* functor = (FunctorT*)const_cast(dataPtr); + return (*functor)(std::forward(args)...); + } template - struct manage_function + struct manage_function { static void call(void* dataPtr, const void* fromPtr, Operation op) { - if (op == Operation::Copy) - { - FunctorT* thisFunctor = reinterpret_cast(dataPtr); - const FunctorT* source = (const FunctorT*)const_cast(fromPtr); - new (thisFunctor) FunctorT(*source); - } - else + FunctorT* thisFunctor = reinterpret_cast(dataPtr); + switch (op) { - manage_function::call(dataPtr, fromPtr, op); + case Operation::Destroy: + { + thisFunctor->~FunctorT(); + break; + } + case Operation::Move: + { + FunctorT* source = (FunctorT*)fromPtr; + new (thisFunctor) FunctorT(std::move(*source)); + break; + } + default: + { + assert(false && "Unexpected operation"); + } } } }; diff --git a/Source/Shared/arcana/threading/dispatcher.h b/Source/Shared/arcana/threading/dispatcher.h index fbcb4b0..080b000 100644 --- a/Source/Shared/arcana/threading/dispatcher.h +++ b/Source/Shared/arcana/threading/dispatcher.h @@ -15,7 +15,7 @@ namespace arcana class dispatcher { public: - using callback_t = stdext::inplace_function; + using callback_t = stdext::inplace_function; static constexpr size_t work_size = WorkSize; template From 85db25fb0b742b6a507cbcd0ce45304dce478cf0 Mon Sep 17 00:00:00 2001 From: syntheticmagus <33846034+syntheticmagus@users.noreply.github.com> Date: Mon, 14 Mar 2022 17:13:51 -0700 Subject: [PATCH 2/2] Consolidation to reduce code duplication. --- .../arcana/functional/inplace_function.h | 670 +++++++----------- 1 file changed, 271 insertions(+), 399 deletions(-) diff --git a/Source/Shared/arcana/functional/inplace_function.h b/Source/Shared/arcana/functional/inplace_function.h index 8300988..bee4aab 100644 --- a/Source/Shared/arcana/functional/inplace_function.h +++ b/Source/Shared/arcana/functional/inplace_function.h @@ -34,20 +34,233 @@ namespace stdext { - constexpr size_t InplaceFunctionDefaultCapacity = 32; - - struct inplace_function_operation + namespace internals { - enum class operations_enum + constexpr size_t InplaceFunctionDefaultCapacity = 32; + + struct inplace_function_operation { - Destroy, - Copy, - Move + enum class operations_enum + { + Destroy, + Copy, + Move + }; }; - }; + + template + struct manage_function + { + using Operation = inplace_function_operation::operations_enum; + static void call(void* dataPtr, const void* fromPtr, Operation op) + { + FunctorT* thisFunctor = reinterpret_cast(dataPtr); + switch (op) + { + case Operation::Copy: + { + if constexpr (SupportsCopy) + { + const FunctorT* source = (const FunctorT*)const_cast(fromPtr); + new (thisFunctor) FunctorT(*source); + break; + } + else + { + assert(false && "Unexpected operation"); + } + } + case Operation::Destroy: + { + thisFunctor->~FunctorT(); + break; + } + case Operation::Move: + { + FunctorT* source = (FunctorT*)fromPtr; + new (thisFunctor) FunctorT(std::move(*source)); + break; + } + default: + { + assert(false && "Unexpected operation"); + } + } + } + }; + + template + struct inplace_function_impl + { + using BufferType = typename std::aligned_storage::type; + + inplace_function_impl() + : m_InvokeFctPtr(&DefaultFunction) + , m_ManagerFctPtr(nullptr) + {} + + // Converts to 'true' if assigned + explicit operator bool() const throw() + { + return m_InvokeFctPtr != &DefaultFunction; + } + + // Invokes the target + // Throws std::bad_function_call if not assigned + RetT operator()(ArgsT... args) const + { + return m_InvokeFctPtr(std::forward(args)..., data()); + } + + void swap(inplace_function_impl& other) + { + BufferType tempData; + this->move(m_Data, tempData); + other.move(other.m_Data, m_Data); + this->move(tempData, other.m_Data); + std::swap(m_InvokeFctPtr, other.m_InvokeFctPtr); + std::swap(m_ManagerFctPtr, other.m_ManagerFctPtr); + } + + void clear() + { + m_InvokeFctPtr = &DefaultFunction; + if (m_ManagerFctPtr) + m_ManagerFctPtr(data(), nullptr, Operation::Destroy); + m_ManagerFctPtr = nullptr; + } + + template + void copy(const inplace_function_impl& other) + { + static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); + static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + + if (other.m_ManagerFctPtr) + other.m_ManagerFctPtr(data(), other.data(), Operation::Copy); + + m_InvokeFctPtr = other.m_InvokeFctPtr; + m_ManagerFctPtr = other.m_ManagerFctPtr; + } + + void move(BufferType& from, BufferType& to) + { + if (m_ManagerFctPtr) + m_ManagerFctPtr(&from, &to, Operation::Move); + else + to = from; + } + + template + void move(inplace_function_impl&& other) + { + static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); + static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); + static_assert(!Copyable || OtherCopyable, "Cannot move an uncopyable inplace_function into a copyable one"); + + if (other.m_ManagerFctPtr) + other.m_ManagerFctPtr(data(), other.data(), Operation::Move); + + m_InvokeFctPtr = other.m_InvokeFctPtr; + m_ManagerFctPtr = other.m_ManagerFctPtr; + + other.m_InvokeFctPtr = &DefaultFunction; + // don't reset the others management function + // because it still needs to destroy the lambda its holding. + } + + void* data() + { + return &m_Data; + } + const void* data() const + { + return &m_Data; + } + + using CompatibleFunctionPointer = RetT (*)(ArgsT...); + using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); + using Operation = internals::inplace_function_operation::operations_enum; + using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); + + InvokeFctPtrType m_InvokeFctPtr; + ManagerFctPtrType m_ManagerFctPtr; + + BufferType m_Data; + + static RetT DefaultFunction(ArgsT..., const void*) + { + throw std::bad_function_call(); + } + + void set(std::nullptr_t) + { + m_ManagerFctPtr = nullptr; + m_InvokeFctPtr = &DefaultFunction; + } + + // For function pointers + void set(CompatibleFunctionPointer ptr) + { + // this is dodgy, and - according to standard - undefined behaviour. But it works + // see: http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type + m_ManagerFctPtr = nullptr; + m_InvokeFctPtr = reinterpret_cast(ptr); + } + + // Set - for functors + // enable_if makes sure this is excluded for function references and pointers. + template + typename std::enable_if::value && !std::is_function::value>::type + set(const FunctorArgT& ftor) + { + using FunctorT = typename std::remove_reference::type; + static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); + static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); + + // copy functor into the mem buffer + FunctorT* buffer = reinterpret_cast(&m_Data); + new (buffer) FunctorT(ftor); + + // generate destructor, copy-constructor and move-constructor + m_ManagerFctPtr = &manage_function::call; + + // generate entry call + m_InvokeFctPtr = &invoke; + } + + // Set - for functors + // enable_if makes sure this is excluded for function references and pointers. + template + typename std::enable_if::value && !std::is_function::value>::type + set(FunctorArgT&& ftor) + { + using FunctorT = typename std::remove_reference::type; + static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); + static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); + + // copy functor into the mem buffer + FunctorT* buffer = reinterpret_cast(&m_Data); + new (buffer) FunctorT(std::move(ftor)); + + // generate destructor, copy-constructor and move-constructor + m_ManagerFctPtr = &manage_function::call; + + // generate entry call + m_InvokeFctPtr = &invoke; + } + + template + static RetT invoke(ArgsT... args, const void* dataPtr) + { + FunctorT* functor = (FunctorT*)const_cast(dataPtr); + return (*functor)(std::forward(args)...); + } + }; + } template class /*alignas(Alignment)*/ inplace_function; @@ -61,16 +274,13 @@ namespace stdext // TODO create free operator overloads, to handle switched arguments - // Creates and empty inplace_function - inplace_function() - : m_InvokeFctPtr(&DefaultFunction) - , m_ManagerFctPtr(nullptr) - {} + // Creates an empty inplace_function + inplace_function() = default; // Destroys the inplace_function. If the stored callable is valid, it is destroyed also ~inplace_function() { - this->clear(); + m_impl.clear(); } // Creates an implace function, copying the target of other within the internal buffer @@ -79,7 +289,7 @@ namespace stdext template inplace_function(const CallableT& c) { - this->set(c); + m_impl.set(c); } // Moves the target of an implace function, storing the callable within the internal buffer @@ -88,21 +298,21 @@ namespace stdext template::value>::type> inplace_function(CallableT&& c) { - this->set(std::move(c)); + m_impl.set(std::move(c)); } // Copy construct an implace_function, storing a copy of other’s target internally // May throw any exception encountered by the constructor when copying the target object inplace_function(const inplace_function& other) { - this->copy(other); + m_impl.copy(other.m_impl); } // Move construct an implace_function, moving the other’s target to this inplace_function’s internal buffer // May throw any exception encountered by the constructor when moving the target object inplace_function(inplace_function&& other) { - this->move(std::move(other)); + m_impl.move(std::move(other.m_impl)); } // Allows for copying from inplace_function object of the same type, but with a smaller buffer @@ -111,7 +321,7 @@ namespace stdext template inplace_function(const inplace_function& other) { - this->copy(other); + m_impl.copy(other.m_impl); } // Allows for moving an inplace_function object of the same type, but with a smaller buffer @@ -120,15 +330,15 @@ namespace stdext template inplace_function(inplace_function&& other) { - this->move(std::move(other)); + m_impl.move(std::move(other.m_impl)); } // Assigns a copy of other’s target // May throw any exception encountered by the assignment operator when copying the target object inplace_function& operator=(const inplace_function& other) { - this->clear(); - this->copy(other); + m_impl.clear(); + m_impl.copy(other.m_impl); return *this; } @@ -136,8 +346,8 @@ namespace stdext // May throw any exception encountered by the assignment operator when moving the target object inplace_function& operator=(inplace_function&& other) { - this->clear(); - this->move(std::move(other)); + m_impl.clear(); + m_impl.move(std::move(other.m_impl)); return *this; } @@ -147,8 +357,8 @@ namespace stdext template inplace_function& operator=(const inplace_function& other) { - this->clear(); - this->copy(other); + m_impl.clear(); + m_impl.copy(other.m_impl); return *this; } @@ -158,8 +368,8 @@ namespace stdext template inplace_function& operator=(inplace_function&& other) { - this->clear(); - this->move(std::move(other)); + m_impl.clear(); + m_impl.move(std::move(other.m_impl)); return *this; } @@ -168,8 +378,8 @@ namespace stdext template::value>::type> inplace_function& operator=(const CallableT& target) { - this->clear(); - this->set(target); + m_impl.clear(); + m_impl.set(target); return *this; } @@ -178,8 +388,8 @@ namespace stdext template inplace_function& operator=(Callable&& target) { - this->clear(); - this->set(std::move(target)); + m_impl.clear(); + m_impl.set(std::move(target)); return *this; } @@ -200,196 +410,25 @@ namespace stdext // Converts to 'true' if assigned explicit operator bool() const throw() { - return m_InvokeFctPtr != &DefaultFunction; + return m_impl.operator bool(); } // Invokes the target // Throws std::bad_function_call if not assigned RetT operator()(ArgsT... args) const { - return m_InvokeFctPtr(std::forward(args)..., data()); + return m_impl(std::forward(args)...); } // Swaps two targets void swap(inplace_function& other) { - BufferType tempData; - this->move(m_Data, tempData); - other.move(other.m_Data, m_Data); - this->move(tempData, other.m_Data); - std::swap(m_InvokeFctPtr, other.m_InvokeFctPtr); - std::swap(m_ManagerFctPtr, other.m_ManagerFctPtr); + m_impl.swap(other.m_impl); } private: - using BufferType = typename std::aligned_storage::type; - void clear() - { - m_InvokeFctPtr = &DefaultFunction; - if (m_ManagerFctPtr) - m_ManagerFctPtr(data(), nullptr, Operation::Destroy); - m_ManagerFctPtr = nullptr; - } - - template - void copy(const inplace_function& other) - { - static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); - static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); - - if (other.m_ManagerFctPtr) - other.m_ManagerFctPtr(data(), other.data(), Operation::Copy); - - m_InvokeFctPtr = other.m_InvokeFctPtr; - m_ManagerFctPtr = other.m_ManagerFctPtr; - } - - void move(BufferType& from, BufferType& to) - { - if (m_ManagerFctPtr) - m_ManagerFctPtr(&from, &to, Operation::Move); - else - to = from; - } - - template - void move(inplace_function&& other) - { - static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); - static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); - static_assert(OtherCopyable, "Cannot move an uncopyable inplace_function into a copyable one"); - - if (other.m_ManagerFctPtr) - other.m_ManagerFctPtr(data(), other.data(), Operation::Move); - - m_InvokeFctPtr = other.m_InvokeFctPtr; - m_ManagerFctPtr = other.m_ManagerFctPtr; - - other.m_InvokeFctPtr = &DefaultFunction; - // don't reset the others management function - // because it still needs to destroy the lambda its holding. - } - - void* data() - { - return &m_Data; - } - const void* data() const - { - return &m_Data; - } - - using CompatibleFunctionPointer = RetT (*)(ArgsT...); - using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); - using Operation = inplace_function_operation::operations_enum; - using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); - - InvokeFctPtrType m_InvokeFctPtr; - ManagerFctPtrType m_ManagerFctPtr; - - BufferType m_Data; - - static RetT DefaultFunction(ArgsT..., const void*) - { - throw std::bad_function_call(); - } - - void set(std::nullptr_t) - { - m_ManagerFctPtr = nullptr; - m_InvokeFctPtr = &DefaultFunction; - } - - // For function pointers - void set(CompatibleFunctionPointer ptr) - { - // this is dodgy, and - according to standard - undefined behaviour. But it works - // see: http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type - m_ManagerFctPtr = nullptr; - m_InvokeFctPtr = reinterpret_cast(ptr); - } - - // Set - for functors - // enable_if makes sure this is excluded for function references and pointers. - template - typename std::enable_if::value && !std::is_function::value>::type - set(const FunctorArgT& ftor) - { - using FunctorT = typename std::remove_reference::type; - static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); - static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); - - // copy functor into the mem buffer - FunctorT* buffer = reinterpret_cast(&m_Data); - new (buffer) FunctorT(ftor); - - // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; - - // generate entry call - m_InvokeFctPtr = &invoke; - } - - // Set - for functors - // enable_if makes sure this is excluded for function references and pointers. - template - typename std::enable_if::value && !std::is_function::value>::type - set(FunctorArgT&& ftor) - { - using FunctorT = typename std::remove_reference::type; - static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); - static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); - - // copy functor into the mem buffer - FunctorT* buffer = reinterpret_cast(&m_Data); - new (buffer) FunctorT(std::move(ftor)); - - // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; - - // generate entry call - m_InvokeFctPtr = &invoke; - } - - template - static RetT invoke(ArgsT... args, const void* dataPtr) - { - FunctorT* functor = (FunctorT*)const_cast(dataPtr); - return (*functor)(std::forward(args)...); - } - - template - struct manage_function - { - static void call(void* dataPtr, const void* fromPtr, Operation op) - { - FunctorT* thisFunctor = reinterpret_cast(dataPtr); - switch (op) - { - case Operation::Copy: - { - const FunctorT* source = (const FunctorT*)const_cast(fromPtr); - new (thisFunctor) FunctorT(*source); - break; - } - case Operation::Destroy: - { - thisFunctor->~FunctorT(); - break; - } - case Operation::Move: - { - FunctorT* source = (FunctorT*)fromPtr; - new (thisFunctor) FunctorT(std::move(*source)); - break; - } - default: - { - assert(false && "Unexpected operation"); - } - } - } - }; + using ImplT = internals::inplace_function_impl; + ImplT m_impl{}; }; template @@ -401,16 +440,13 @@ namespace stdext // TODO create free operator overloads, to handle switched arguments - // Creates and empty inplace_function - inplace_function() - : m_InvokeFctPtr(&DefaultFunction) - , m_ManagerFctPtr(nullptr) - {} + // Creates an empty inplace_function + inplace_function() = default; // Destroys the inplace_function. If the stored callable is valid, it is destroyed also ~inplace_function() { - this->clear(); + m_impl.clear(); } // Creates an implace function, copying the target of other within the internal buffer @@ -419,7 +455,7 @@ namespace stdext template inplace_function(const CallableT& c) { - this->set(c); + m_impl.set(c); } // Moves the target of an implace function, storing the callable within the internal buffer @@ -428,7 +464,7 @@ namespace stdext template::value>::type> inplace_function(CallableT&& c) { - this->set(std::move(c)); + m_impl.set(std::move(c)); } // Copy construction is not possible from uncopyable inplace_functions. @@ -438,7 +474,7 @@ namespace stdext // May throw any exception encountered by the constructor when moving the target object inplace_function(inplace_function&& other) { - this->move(std::move(other)); + m_impl.move(std::move(other.m_impl)); } // Allows for copying from inplace_function object of the same type, but with a smaller buffer @@ -447,7 +483,7 @@ namespace stdext template inplace_function(const inplace_function& other) { - this->copy(other); + m_impl.copy(other.m_impl); } // Allows for moving an inplace_function object of the same type, but with a smaller buffer @@ -456,7 +492,7 @@ namespace stdext template inplace_function(inplace_function&& other) { - this->move(std::move(other)); + m_impl.move(std::move(other.m_impl)); } // Copy assignment is not possible from uncopyable inplace_functions @@ -466,8 +502,8 @@ namespace stdext // May throw any exception encountered by the assignment operator when moving the target object inplace_function& operator=(inplace_function&& other) { - this->clear(); - this->move(std::move(other)); + m_impl.clear(); + m_impl.move(std::move(other.m_impl)); return *this; } @@ -477,8 +513,8 @@ namespace stdext template inplace_function& operator=(const inplace_function& other) { - this->clear(); - this->copy(other); + m_impl.clear(); + m_impl.copy(other); return *this; } @@ -488,8 +524,8 @@ namespace stdext template inplace_function& operator=(inplace_function&& other) { - this->clear(); - this->move(std::move(other)); + m_impl.clear(); + m_impl.move(std::move(other)); return *this; } @@ -498,8 +534,8 @@ namespace stdext template::value>::type> inplace_function& operator=(const CallableT& target) { - this->clear(); - this->set(target); + m_impl.clear(); + m_impl.set(target); return *this; } @@ -508,8 +544,8 @@ namespace stdext template inplace_function& operator=(Callable&& target) { - this->clear(); - this->set(std::move(target)); + m_impl.clear(); + m_impl.set(std::move(target)); return *this; } @@ -530,188 +566,24 @@ namespace stdext // Converts to 'true' if assigned explicit operator bool() const throw() { - return m_InvokeFctPtr != &DefaultFunction; + return m_impl.operator bool(); } // Invokes the target // Throws std::bad_function_call if not assigned RetT operator()(ArgsT... args) const { - return m_InvokeFctPtr(std::forward(args)..., data()); + return m_impl(std::forward(args)...); } // Swaps two targets void swap(inplace_function& other) { - BufferType tempData; - this->move(m_Data, tempData); - other.move(other.m_Data, m_Data); - this->move(tempData, other.m_Data); - std::swap(m_InvokeFctPtr, other.m_InvokeFctPtr); - std::swap(m_ManagerFctPtr, other.m_ManagerFctPtr); + m_impl.swap(other.m_impl); } private: - using BufferType = typename std::aligned_storage::type; - void clear() - { - m_InvokeFctPtr = &DefaultFunction; - if (m_ManagerFctPtr) - m_ManagerFctPtr(data(), nullptr, Operation::Destroy); - m_ManagerFctPtr = nullptr; - } - - template - void copy(const inplace_function& other) - { - static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); - static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); - - if (other.m_ManagerFctPtr) - other.m_ManagerFctPtr(data(), other.data(), Operation::Copy); - - m_InvokeFctPtr = other.m_InvokeFctPtr; - m_ManagerFctPtr = other.m_ManagerFctPtr; - } - - void move(BufferType& from, BufferType& to) - { - if (m_ManagerFctPtr) - m_ManagerFctPtr(&from, &to, Operation::Move); - else - to = from; - } - - template - void move(inplace_function&& other) - { - static_assert(OtherCapacity <= Capacity, "Can't squeeze larger inplace_function into a smaller one"); - static_assert(Alignment % OtherAlignment == 0, "Incompatible alignments"); - - if (other.m_ManagerFctPtr) - other.m_ManagerFctPtr(data(), other.data(), Operation::Move); - - m_InvokeFctPtr = other.m_InvokeFctPtr; - m_ManagerFctPtr = other.m_ManagerFctPtr; - - other.m_InvokeFctPtr = &DefaultFunction; - // don't reset the others management function - // because it still needs to destroy the lambda its holding. - } - - void* data() - { - return &m_Data; - } - const void* data() const - { - return &m_Data; - } - - using CompatibleFunctionPointer = RetT (*)(ArgsT...); - using InvokeFctPtrType = RetT (*)(ArgsT..., const void* thisPtr); - using Operation = inplace_function_operation::operations_enum; - using ManagerFctPtrType = void (*)(void* thisPtr, const void* fromPtr, Operation); - - InvokeFctPtrType m_InvokeFctPtr; - ManagerFctPtrType m_ManagerFctPtr; - - BufferType m_Data; - - static RetT DefaultFunction(ArgsT..., const void*) - { - throw std::bad_function_call(); - } - - void set(std::nullptr_t) - { - m_ManagerFctPtr = nullptr; - m_InvokeFctPtr = &DefaultFunction; - } - - // For function pointers - void set(CompatibleFunctionPointer ptr) - { - // this is dodgy, and - according to standard - undefined behaviour. But it works - // see: http://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type - m_ManagerFctPtr = nullptr; - m_InvokeFctPtr = reinterpret_cast(ptr); - } - - // Set - for functors - // enable_if makes sure this is excluded for function references and pointers. - template - typename std::enable_if::value && !std::is_function::value>::type - set(const FunctorArgT& ftor) - { - using FunctorT = typename std::remove_reference::type; - static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); - static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); - - // copy functor into the mem buffer - FunctorT* buffer = reinterpret_cast(&m_Data); - new (buffer) FunctorT(ftor); - - // generate destructor, copy-constructor and move-constructor - m_ManagerFctPtr = &manage_function::call; - - // generate entry call - m_InvokeFctPtr = &invoke; - } - - // Set - for functors - // enable_if makes sure this is excluded for function references and pointers. - template - typename std::enable_if::value && !std::is_function::value>::type - set(FunctorArgT&& ftor) - { - using FunctorT = typename std::remove_reference::type; - static_assert(sizeof(FunctorT) <= Capacity, "Functor too big to fit in the buffer"); - static_assert(Alignment % alignof(FunctorArgT) == 0, "Incompatible alignment"); - - // copy functor into the mem buffer - FunctorT* buffer = reinterpret_cast(&m_Data); - new (buffer) FunctorT(std::move(ftor)); - - // generate destructor and move-constructor - m_ManagerFctPtr = &manage_function::call; - - // generate entry call - m_InvokeFctPtr = &invoke; - } - - template - static RetT invoke(ArgsT... args, const void* dataPtr) - { - FunctorT* functor = (FunctorT*)const_cast(dataPtr); - return (*functor)(std::forward(args)...); - } - - template - struct manage_function - { - static void call(void* dataPtr, const void* fromPtr, Operation op) - { - FunctorT* thisFunctor = reinterpret_cast(dataPtr); - switch (op) - { - case Operation::Destroy: - { - thisFunctor->~FunctorT(); - break; - } - case Operation::Move: - { - FunctorT* source = (FunctorT*)fromPtr; - new (thisFunctor) FunctorT(std::move(*source)); - break; - } - default: - { - assert(false && "Unexpected operation"); - } - } - } - }; + using ImplT = internals::inplace_function_impl; + ImplT m_impl{}; }; }