Простой функциональный объект, служащий заменой std::function

Как написать функтор Func, чтобы вместо его экземпляра можно было передать и лямбду, не используя std::function<void()>? Например, написав функцию:

void exec(Func f)
{        
    f();
}

Вызвать ее так:

exec([]() {std::cout << "Hello world\n"; });

Не интересно как можно менять сигнатуру функции, чтобы она принимала и лямбду, а интересно как написать функтор, чтобы можно было передать лямбду в функцию с приведенной сигнатурой.( спортивный интерес).


Ответы (2 шт):

Автор решения: bipll
template<class F> void exec(F f) {
    f();
}

?

→ Ссылка
Автор решения: HolyBlackCat

Максимально простой вариант:

class Func
{
    struct Wrapper
    {
        virtual void invoke() = 0;
        virtual ~Wrapper() {}
    };

    std::unique_ptr<Wrapper> wrapper_ptr;

  public:
    Func() {}

    template <
        typename T,
        typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, Func>>
    >
    Func(T &&obj)
    {
        using type = std::decay_t<T>;

        struct ThisWrapper : Wrapper
        {
            type obj;
            ThisWrapper(const type &obj) : obj(obj) {}
            ThisWrapper(type &&obj) : obj(std::move(obj)) {}

            void invoke() override
            {
                return obj();
            }
        };
        wrapper_ptr = std::make_unique<ThisWrapper>(std::forward<T>(obj));
    }

    void operator()()
    {
        wrapper_ptr->invoke();
    }
};

Более правильный вариант, с передачей параметров и возвратом значения, и с возможностью копирования.

По-хорошему нужно еще сделать так, чтобы для маленьких лямбд не выделять память в куче, тогда будет совсем как std::function.

#include <memory>
#include <type_traits>
#include <utility>

template <typename>
class Func;

template <typename R, typename ...P>
class Func<R(P...)>
{
    struct Wrapper;
    using wrapper_ptr_t = std::unique_ptr<Wrapper>;
    wrapper_ptr_t wrapper_ptr;

    struct Wrapper
    {
        virtual wrapper_ptr_t clone() const = 0;
        virtual R invoke(P ... params) = 0;
        virtual ~Wrapper() {}
    };

  public:
    Func() {}

    Func(const Func &other)
        : wrapper_ptr(other ? other.wrapper_ptr->clone() : wrapper_ptr_t{})
    {}
    Func(Func &&other) noexcept = default;

    Func &operator=(const Func &other)
    {
        Func copy(other);
        std::swap(wrapper_ptr, copy.wrapper_ptr);
        return *this;
    }
    Func &operator=(Func &&other) noexcept = default;


    template <
        typename T,
        typename = std::enable_if_t<
            !std::is_same_v<std::decay_t<T>, Func> && std::is_convertible_v<
                decltype(std::declval<std::decay_t<T> &>()(std::declval<P>()...)),
                R
            >
        >
    >
    Func(T &&obj)
    {
        using type = std::decay_t<T>;

        struct ThisWrapper : Wrapper
        {
            type obj;
            ThisWrapper(const type &obj) : obj(obj) {}
            ThisWrapper(type &&obj) : obj(std::move(obj)) {}

            wrapper_ptr_t clone() const override
            {
                return std::make_unique<ThisWrapper>(obj);
            }
            R invoke(P ... params) override
            {
                return obj(std::forward<P>(params)...);
            }
        };
        wrapper_ptr = std::make_unique<ThisWrapper>(std::forward<T>(obj));
    }

    explicit operator bool() const
    {
        return bool(wrapper_ptr);
    }

    template <typename ...Params>
    R operator()(Params ... params)
    {
        return wrapper_ptr->invoke(std::forward<Params>(params)...);
    }
};

Использование:

#include <iostream>

void exec(Func<void()> f)
{        
    f();
}

int main()
{
    exec([]() {std::cout << "Hello world\n"; });
}
→ Ссылка