#pragma once

#include <util/generic/hash.h>

#include <memory>

namespace NSv {
    namespace NPrivate {
        // The name looks better in perf reports when this is not a lambda.
        template <typename R, typename F, typename... Args>
        static R CastInvoke(void* p, Args&&... args) {
            return (*reinterpret_cast<F*>(p))(std::forward<Args>(args)...);
        }
    }

    // Unlike std::function, can be constructed from non-copyable objects.
    template <typename F>
    struct TFunction;

    template <typename R, typename... Args>
    struct TFunction<R(Args...)> {
    public:
        TFunction() = default;

        template <typename F>
        TFunction(std::shared_ptr<F> p) noexcept
            : F_(std::move(p))
            , C_(&NPrivate::CastInvoke<R, F, Args...>)
        {
        }

        template <typename F, typename = std::enable_if_t<!std::is_same<F, TFunction>::value>>
        TFunction(F f)
            : TFunction(std::make_shared<F>(std::move(f)))
        {
        }

        explicit operator bool() const {
            return !!F_;
        }

        R operator()(Args... args) const {
            return C_(F_.get(), std::forward<Args>(args)...);
        }

    private:
        std::shared_ptr<void> F_;
        R (*C_)(void*, Args&&...);
    };
}
