gists/cpp/inplace-function.md

3.9 KiB

#pragma once


#include <cstddef>
#include <new>
#include <type_traits>
#include <utility>


namespace util {


///
/// @brief Class storing a type-erased function object/pointer.
/// @details This class does not employ dynamic memory allocation; instead, it
/// stores the function object/pointer in a fixed-size buffer, the size of which
/// can be specified as a template parameter.
/// @warning This class does not take ownership of any resources it is
/// constructed from. It is the responsibility of the user to ensure that the
/// function object/pointer outlives the InplaceFunction object.
///
/// @tparam func_sig_t Signature of the function.
/// @tparam t_storage_size Size of the storage buffer, in bytes.
///
template <typename func_sig_t, std::size_t t_storage_size = 8>
class InplaceFunction;

template <typename return_t, typename... args_t, std::size_t storage_size>
class InplaceFunction<return_t(args_t...), storage_size> {

    using storage_t      = char[storage_size];
    using wrapper_func_t = return_t (*)(storage_t, args_t...);

public:
    InplaceFunction() = default;

    ///
    /// @brief Constructor for function objects.
    ///
    template <typename F>
    InplaceFunction(F f) {
        bind(f);
    }

    ///
    /// @brief Construct from free function pointer.
    ///
    template <auto F>
    static InplaceFunction construct() {
        InplaceFunction<return_t(args_t...)> func;
        func.template bind<F>();
        return func;
    }

    ///
    /// @brief Construct form member function pointer.
    ///
    template <auto F, typename class_t>
    static InplaceFunction construct(class_t& c) {
        InplaceFunction<return_t(args_t...)> func;
        func.template bind<F>(c);
        return func;
    }

    ///
    /// @brief Construct from function object.
    ///
    template <typename F>
    static InplaceFunction construct(F f) {
        InplaceFunction<return_t(args_t...)> func;
        func.bind(f);
        return func;
    }

    ///
    /// @warning If no callable has been bound, calling this function will
    /// result in undefined behavior.
    ///
    return_t operator()(args_t... args) {
        return mWrapper(mStorage, args...);
    }

    operator bool() const {
        return !(mWrapper == nullptr);
    }

    ///
    /// @brief Bind a free function.
    ///
    /// @tparam F Pointer to free function
    ///
    template <auto F>
        requires std::is_invocable_r_v<return_t, decltype(F), args_t...>
    void bind() {
        mWrapper = [](storage_t, args_t... args) {
            return F(std::forward<args_t>(args)...);
            // return std::invoke(F, std::forward<args_t>(args)...);
        };
    }

    ///
    /// @brief Bind a member function.
    ///
    /// @tparam class_t Class the member belongs to.
    /// @param c Reference to object the member function should be called on.
    /// @tparam F Member function pointer.
    ///
    template <auto F, typename class_t>
        requires std::is_invocable_r_v<return_t, decltype(F), class_t&,
                                       args_t...> &&
                 (sizeof(class_t*) <= storage_size)
    void bind(class_t& c) {
        new (mStorage)(class_t*){&c};

        mWrapper = [](storage_t storage, args_t... args) {
            return F(*reinterpret_cast<class_t**>(storage),
                     std::forward<args_t>(args)...);
        };
    }

    ///
    /// @brief Bind a function object.
    ///
    template <typename F>
        requires std::is_invocable_r_v<return_t, F, args_t...> &&
                 (sizeof(F) <= storage_size) &&
                 std::is_trivially_destructible_v<F>
    void bind(F f) {
        new (mStorage) F{std::move(f)};
        mWrapper = [](storage_t storage, args_t... args) {
            return (*reinterpret_cast<F*>(storage))(
                std::forward<args_t>(args)...);
        };
    }

private:
    storage_t      mStorage;
    wrapper_func_t mWrapper = nullptr;
};


} // namespace util