Хранение лямбда-функций нескольких типов в одной переменной

0

У меня есть свой собственный класс Runnable который может хранить указатель на указатель, функцию-член (возможно, лямбда-указатель) или функтор, и запускать его одним способом. Здесь приведенная версия:

template<typename returnType, typename... args>
class Runnable
{
private:
    template<typename rreturnType, typename... aargs>
    struct FuncBase
    {
        virtual returnType run(aargs...) const = 0;
        virtual FuncBase<rreturnType, aargs...>* clone() const = 0;
        virtual ~FuncBase() { }
    };
    template<typename rreturnType, typename... aargs>
    struct Func : FuncBase<rreturnType, aargs...>
    {
        rreturnType (*func) (aargs...);
        rreturnType run(aargs... arg) const { return func(arg...); }

        FuncBase<rreturnType, aargs...>* clone() const { return new Func<rreturnType, args...>(func); }

        Func(rreturnType (*func) (aargs...)) : func(func) { }
    };

    template<typename memberType, typename rreturnType, typename... aargs>
    struct MemberFunc : public FuncBase<rreturnType, aargs...>
    {
        memberType& owner;
        rreturnType (memberType::*member) (args...);
        rreturnType run(aargs... arg) const { return (owner.*member)(arg...); }

        FuncBase<rreturnType, aargs...>* clone() const { return new MemberFunc<memberType, rreturnType, aargs...>(owner, member); }

        MemberFunc(memberType& owner, rreturnType (memberType::*member) (aargs...)) : owner(owner), member(member) { }
    };

    template<typename functorType, typename rreturnType, typename... aargs>
    struct FunctorFunc : public FuncBase<rreturnType, aargs...>
    {
        functorType& owner;
        rreturnType run(aargs... arg) const { return owner(arg...); }

        FuncBase<rreturnType, aargs...>* clone() const { return new FunctorFunc<functorType, rreturnType, aargs...>(owner); }

        FunctorFunc(functorType& owner) : owner(owner) { }
    };

    FuncBase<returnType, args...>* f;

    Runnable(const Runnable<returnType, args...>& r) = delete;
    Runnable<returnType, args...>& operator=(const Runnable<returnType, args...>& r) = delete;

public:
    Runnable(returnType (*func) (args...)) { f = new Func<returnType, args...>(func); }
    template<typename memberType>
    Runnable(memberType& owner, returnType (memberType::*member) (args...)) { f = new MemberFunc<memberType, returnType, args...>(owner, member); }
    template<typename functorType>
    Runnable(functorType& owner) { f = new FunctorFunc<functorType, returnType, args...>(owner); }
    ~Runnable() { delete f; }
    returnType operator() (args... arg) const { return f->run(arg...); }
};

И я хочу сохранить таким же образом изменчивую лямбда-функцию

Я могу хранить нормальную лямбду, но когда я пытаюсь сохранить изменяемую лямбду, например:

int fk = 20;
    Runnable<double, double> r([&](double d) mutable -> double { cout << fk; return d; });
    r(1.0);

Я получаю следующую ошибку:

no matching function for call to 'Runnable<double, double>::Runnable(main()::__lambda0)'
     Runnable<double, double> r([&](double d) mutable -> double { cout << fk; return d; });

Когда я удаляю ссылки из Runnable :: FunctorFunc

template<typename functorType, typename rreturnType, typename... aargs>
    struct FunctorFunc : public FuncBase<rreturnType, aargs...>
    {
        functorType owner;
        rreturnType run(aargs... arg) { return owner(arg...); }

        FuncBase<rreturnType, aargs...>* clone() const { return new FunctorFunc<functorType, rreturnType, aargs...>(owner); }

        FunctorFunc(functorType owner) : owner(owner) { }
    };

Это правильно в изменчивых лямбдах, но я получаю следующую ошибку от нормальных лямбда:

cannot allocate an object of abstract type 'Runnable<double, double>::FunctorFunc<tsf::easing::__lambda0, double, double>'
 Runnable(functorType owner) { f = new FunctorFunc<functorType, returnType, args...>(owner); }
                                 ^

                                                                                 ^

Основная проблема заключается в том, как я могу изменить этот класс, чтобы принять lambdas. Или, может быть, у кого-то есть аналогичное классное решение для решения этой проблемы

  • 1
    Просто из любопытства, зачем ты это делаешь? Учитывая, что std::bind , std::function и существуют, и прекрасно справляются со всем, что вы пытаетесь сделать, почему вы создаете этот класс? Разве вы не очень хорошо дублируете вещи, которые «просто работают»? Если чего-то не хватает, скажите, пожалуйста. Смотрите: en.cppreference.com/w/cpp/utility/functional
  • 0
    Я не знал о std :: function и написал Runnable, думая, что не существует стандартной библиотеки для подобных вещей. Я обнаружил std::function сегодня, когда пытался решить эту проблему :)
Показать ещё 2 комментария
Теги:
lambda

1 ответ

0
Лучший ответ

Оценка выражения лямбда приводит к временному присвоению. В вашем конструкторе для объектов-функторов

template<typename functorType> Runnable(functorType& owner) { /*...*/ }

вы пытаетесь связать ссылку non-const lvalue с таким временным. Это не может работать, и оно не будет работать независимо от того, изменена ли лямбда или нет.

Я предполагаю, что он, похоже, работал на непеременную лямбду, потому что вы использовали тот, который ничего не фиксировал; такие объекты неявно конвертируются в указатели на функции, поэтому вызов фактически разрешен конструктору, использующему указатель на функцию.

Вы могли бы объявить конструктор вместо ссылки на пересылку, но вам придется фактически скопировать или переместить аргумент в член owner FunctorFunc. Этот член не может быть ссылкой, так как он может свисать после завершения вызова конструктору - аргумент является временным, если это выражение лямбда, помните?

Здесь модифицированный FunctorFunc который делает то, что я описал выше:

template<typename functorType, typename rreturnType, typename... aargs>
struct FunctorFunc : public FuncBase<rreturnType, aargs...>
{
    functorType owner;
    rreturnType run(aargs... arg) { return owner(arg...); }

    FuncBase<rreturnType, aargs...>* clone() const { return new FunctorFunc<functorType, rreturnType, aargs...>(owner); }

    FunctorFunc(functorType&& owner_) : owner(std::move(owner_)) {}
    FunctorFunc(const functorType& owner_) : owner(owner_) {}
};

И здесь модифицированный конструктор:

template<typename functorType>
Runnable(functorType&& owner) { f = new FunctorFunc<functorType, returnType, args...>(std::forward<functorType>(owner)); }

Обратите особое внимание на все изменения, которые я должен был сделать - все они необходимы: где вещи объявлены как ссылки, а где нет, использование std::forward и std::move. Если что-то неясно, спросите меня. Решение обрабатывает объекты, имеющие значение rvalue и lvalue; rvalues обычно будут lambdas, тогда как lvalues могут быть некоторыми другими типами вызываемых объектов, которые вы создаете отдельно.

Обратите внимание, что мне также пришлось удалить определитель const из объявления функции-члена run(). То, в которое входит mutable часть: оператор вызова функции для не изменяемой лямбда является const, поэтому его можно вызвать в объектах-объектах const. Как только lambda объявляется mutable, это уже не так, поэтому owner больше не может быть const, поэтому удаление классификатора из объявления функции.

И теперь мы переходим к вашей второй проблеме: вам также нужно удалить const из объявления функции-члена run() в абстрактном базовом классе FuncBase. В противном случае вы фактически объявляете другую функцию в FunctorFunc, а не переопределяете ее из базы. Из-за этого FunctorFunc остается абстрактным (он все еще имеет чистую виртуальную функцию, не определенную); это то, что сообщение об ошибке пытается вам рассказать.

Чтобы избежать столкновения с этой проблемой в будущем, используйте спецификатор override - это даст вам ясное сообщение об ошибке.


EDIT: Конструктор Runnable(functorType&& owner) является слишком общим, он может даже действовать как конструктор copy/move в некоторых случаях. Вот решение для его ограничения с помощью кувалды SFINAE.

Первоначально я думал, что должен ограничивать functorType только совпадающими вызываемыми типами, которые принимают args... и возвращают returnType как объявлено в параметрах шаблона для текущего экземпляра Runnable. Для этого параметры шаблона для конструктора должны выглядеть так:

template<typename functorType, typename = std::enable_if_t<
    std::is_same<std::result_of_t<functorType(args...)>, returnType>::value>>

Увы, текущее создание Runnable также является вызываемым типом, удовлетворяющим этому условию, и именно этого мы хотим избежать. Таким образом, решение должно также исключить этот экземпляр Runnable:

template<typename functorType, typename = std::enable_if_t<
    !std::is_same<std::remove_cv_t<std::remove_reference_t<functorType>>, Runnable>::value &&
    std::is_same<std::result_of_t<functorType(args...)>, returnType>::value>>

Runnable с теми же параметрами шаблона, что и текущее создание, завершит первый тест, а Runnable с разными параметрами шаблона выйдет из строя вторым, как и любой вызываемый тип, который не имеет правильных параметров и возвращаемого типа. Максимальное удовольствие с характерными чертами...

В коде предполагается, что вы используете С++ 14, иначе он будет (даже) более подробным.

  • 0
    Спасибо. Я попробую :)
  • 0
    Это компилируется. Но у вашего решения есть очень странный симптом: оно вызывает SIGSEGV в strchr () ДО вызова main (), если я вызываю следующую (пустую) функцию (эта функция из template<typename T> class Tween класса template<typename T> class Tween : template<typename T> Tween<T>::Tween(T& obj, const Properties<T>& to, sf::Time duration, Easing ease) : obj(obj), to(to), duration(duration), progress(0.0), ease(ease) . Очень странно. Я держу отладки.
Показать ещё 8 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню