6
\$\begingroup\$

The Unique Function is supposed to be able to replace std::function in most situations where you do not need to be able to copy the functions, just move them. This has the primary advantage of being able to take move-only function objects, including lambdas that has gotten an std::promise moved into them. Also feature small buffer optimization.

unique_function.h

#pragma once

#include "core/memory/small_buffer.h"

namespace alchemist
{
    namespace core
    {
        template <typename Signature>
        class UniqueFunction;

        /**
        * Move only function implementation that assumes ownership over passed functions,
        * mainly useful in that it accepts function objects that themselves are move-only.
        * Utilizes small buffer optimization internally
        * @tparam RetT  the return type of any functions
        * @tparam Args  the types of the arguments to the function
        **/
        template<typename RetT, typename... Args>
        class UniqueFunction<RetT(Args...)>
        {
        public:
            /**
            * Constructor
            * @tparam FuncT the type of the passed function
            * @param function   the function held by this UniqueFunction
            **/
            template <typename FuncT>
            UniqueFunction(FuncT&& function);

            UniqueFunction(const UniqueFunction<RetT(Args...)>& rhs) = delete;
            UniqueFunction(UniqueFunction<RetT(Args...)>&& rhs) = default;

            UniqueFunction<RetT(Args...)>& operator=(const UniqueFunction<RetT(Args...)>& rhs) = delete;
            UniqueFunction<RetT(Args...)>& operator=(UniqueFunction<RetT(Args...)> && rhs) = default;

            /**
            * Calls the function held by this object.
            * @param args   the arguments passed to the held function
            * @returns the return value of the held function
            **/
            RetT operator()(Args&&... args);
        private:
            class IFunctionHolder
            {
            public:
                virtual RetT operator()(Args&&...) = 0;
            };

            template<typename FuncT> 
            class FunctionHolder : public IFunctionHolder
            {
            public:
                virtual ~FunctionHolder() = default;

                FunctionHolder(FuncT&& function);

                virtual RetT operator()(Args&&...) override;
            private:
                FuncT m_function;
            };
            SmallBuffer<sizeof(void*) * 4, IFunctionHolder> m_function_buffer;
        };
    }
}

#include "core/functional/detail/unique_function.hpp"

detail/unique_function.hpp

#pragma once

#include "core/functional/unique_function.h"

namespace alchemist
{
    namespace core
    {
            template<typename RetT, typename... Args>
            template <typename FuncT>
            UniqueFunction<RetT(Args...)>::UniqueFunction(FuncT&& function)
            {
                m_function_buffer.emplace < FunctionHolder<FuncT> >(std::forward<FuncT>(function));
            }

            template<typename RetT, typename... Args>
            RetT UniqueFunction<RetT(Args...)>::operator()(Args&&... args)
            {
                return (*m_function_buffer.get())(std::forward<Args>(args)...);
            }

            template<typename RetT, typename... Args>
            template<typename FuncT> 
            UniqueFunction<RetT(Args...)>::FunctionHolder<FuncT>::FunctionHolder(FuncT&& function)
                : m_function(std::forward<FuncT>(function))
            {
            }

            template<typename RetT, typename... Args>
            template<typename FuncT>
            RetT UniqueFunction<RetT(Args...)>::FunctionHolder<FuncT>::operator()(Args&&... args)
            {
                return m_function(std::forward<Args>(args)...);
            }

    }
}

small_buffer.h

#pragma once

#include <variant>
#include <array>
#include <memory>

namespace alchemist
{
    namespace core
    {
        /**
        * Small buffer optimization implementation intended for RTTI-types, ie types that have a virtual function.
        * Move only.
        *
        * @tparam MaxSize   the maximum size of the object before it no longer will be stored locally and
        *                   put on the heap instead
        * @tparam Interface the interface type for objects put in the buffer
        **/
        template<size_t MaxSize, typename Interface>
        class SmallBuffer
        {
        public:

            /**
            * Constructor
            * Produces an empty buffer
            **/
            SmallBuffer();
            template<typename T>

            /**
            * Constructor
            * @param obj    moves the passed object into the buffer, holding it there
            **/
            explicit SmallBuffer(T&& obj);

            SmallBuffer(SmallBuffer<MaxSize, Interface>&& rhs);

            ~SmallBuffer();

            /**
            * Checks if the buffer holds any value or not
            * @returns true if it is empty, false otherwise
            **/
            bool is_empty() const;

            /**
            * Constructs a new concrete object in-place inside the buffer, destroying any
            * old ones held.
            * @tparam T the type of the concrete object to constructor
            * @tparam Args  the types of the arguments to the concrete objects constructor
            * @param args   the args to pass to the concrete objects constructor
            **/
            template<typename T, typename... Args> 
            void emplace(Args&&... args);

            SmallBuffer<MaxSize, Interface>& operator=(SmallBuffer<MaxSize, Interface>&& rhs);

            /**
            * Gets the held object
            * @returns  a pointer to the interface type of the held object
            * @throws   if the buffer is empty
            **/
            Interface* get() const;

            /**
            * Gets the held object
            * @returns  a pointer to the interface type of the held object
            * @throws   if the buffer is empty
            **/
            Interface* operator*() const;

            /**
            * Gets and dereferences the held object immediately
            * @returns  a pointer to the interface type of the held object
            * @throws   if the buffer is empty
            **/
            Interface* operator->() const;
        private:
            void destroy_held();

            class EmptyBuffer
            {
            };

            using buffer_t = std::array<uint8_t, MaxSize>;
            using VariantType = std::variant<EmptyBuffer, Interface*, buffer_t>;

            mutable VariantType m_buffer;
        };
    }
}

#include "core/memory/detail/small_buffer.hpp"

detail/small_buffer.hpp

#pragma once

#include "core/memory/small_buffer.h"

#include <type_traits>

namespace alchemist
{
    namespace core
    {
        template<size_t MaxSize, typename Interface>
        SmallBuffer<MaxSize, Interface>::SmallBuffer()
            : m_buffer(EmptyBuffer())
        {

        }
        template<size_t MaxSize, typename Interface>
        template<typename T>
        SmallBuffer<MaxSize, Interface>::SmallBuffer(T&& obj)
            : m_buffer(EmptyBuffer())
        {
            emplace<T>(std::forward<T>(obj));
        }
        template<size_t MaxSize, typename Interface>
        SmallBuffer<MaxSize, Interface>::SmallBuffer(SmallBuffer<MaxSize, Interface>&& rhs)
        {
            destroy_held();
            m_buffer = std::move(rhs.m_buffer);
            rhs.m_buffer = EmptyBuffer();
        }

        template<size_t MaxSize, typename Interface>
        SmallBuffer<MaxSize, Interface>::~SmallBuffer()
        {
            destroy_held();
        }

        template<size_t MaxSize, typename Interface>
        bool SmallBuffer<MaxSize, Interface>::is_empty() const
        {
            return std::holds_alternative<EmptyBuffer>(m_buffer);
        }

        template<size_t MaxSize, typename Interface>
        template<typename T, typename... Args>
        void SmallBuffer<MaxSize, Interface>::emplace(Args&&... args)
        {
            static_assert(std::is_base_of<Interface, T>::value, "T is not a type that can be put into this small buffer object!");

            if (sizeof(T) > MaxSize)
            {
                m_buffer.emplace<Interface*>(new T(std::forward<Args>(args)...));
            }
            else
            {
                new (m_buffer.emplace<buffer_t>().data()) T(std::forward<Args>(args)...);
            }
        }

        template<size_t MaxSize, typename Interface>
        SmallBuffer<MaxSize, Interface>& SmallBuffer<MaxSize, Interface>::operator=(SmallBuffer<MaxSize, Interface>&& rhs)
        {
            destroy_held();
            m_buffer = std::move(rhs.m_buffer);
            rhs.m_buffer = EmptyBuffer();
            return *this;
        }


        template<size_t MaxSize, typename Interface>
        Interface* SmallBuffer<MaxSize, Interface>::get() const
        {
            if (std::holds_alternative<Interface*>(m_buffer))
            {
                return std::get<Interface*>(m_buffer);
            }
            else if (std::holds_alternative<buffer_t>(m_buffer))
            {
                return reinterpret_cast<typename Interface*>(std::get<buffer_t>(m_buffer).data());
            }
            else
            {
                throw std::logic_error("Attempted to access an Empty small buffer.");
            }
        }

        template<size_t MaxSize, typename Interface>
        Interface* SmallBuffer<MaxSize, Interface>::operator*() const
        {
            return get();
        }

        template<size_t MaxSize, typename Interface>
        Interface* SmallBuffer<MaxSize, Interface>::operator->() const
        {
            return get();
        }

        template<size_t MaxSize, typename Interface>
        void SmallBuffer<MaxSize, Interface>::destroy_held()
        {
            if (std::holds_alternative<Interface*>(m_buffer))
            {
                delete std::get<Interface*>(m_buffer);
                m_buffer = EmptyBuffer();
            }
            else if (std::holds_alternative<buffer_t>(m_buffer))
            {
                reinterpret_cast<typename Interface*>(std::get<buffer_t>(m_buffer).data())->~Interface();
                m_buffer = EmptyBuffer();
            }
            else
            {
                // Holds nothing, do nothing
            }
        }

    }
}

I would be thankfull for any feedback!

\$\endgroup\$
  • 1
    \$\begingroup\$ @MartinYork: I'm unsure which ... you mean. All the ones I know of seem to work just fine. \$\endgroup\$ – rubenvb Aug 22 '18 at 14:25
  • \$\begingroup\$ @MartinYork ... can match zero arguments. \$\endgroup\$ – L. F. Jun 11 at 12:18
  • \$\begingroup\$ @L.F. So it does. Why did I think it could not match zero. Has this changed recently? \$\endgroup\$ – Martin York Jun 11 at 12:49
  • \$\begingroup\$ @MartinYork It has been designed to accept zero arguments since the day it was proposed. Why are you thinking otherwise? ;-) \$\endgroup\$ – L. F. Jun 11 at 13:08
  • \$\begingroup\$ @L.F. I don't know. \$\endgroup\$ – Martin York Jun 11 at 13:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.