У меня есть свой собственный класс 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. Или, может быть, у кого-то есть аналогичное классное решение для решения этой проблемы
Оценка выражения лямбда приводит к временному присвоению. В вашем конструкторе для объектов-функторов
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, иначе он будет (даже) более подробным.
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)
. Очень странно. Я держу отладки.
std::bind
,std::function
и существуют, и прекрасно справляются со всем, что вы пытаетесь сделать, почему вы создаете этот класс? Разве вы не очень хорошо дублируете вещи, которые «просто работают»? Если чего-то не хватает, скажите, пожалуйста. Смотрите: en.cppreference.com/w/cpp/utility/functionalstd::function
сегодня, когда пытался решить эту проблему :)