我有自己的Runnable
类,可以存储函数指针,成员函数(可能是lambda)指针或函子,并以一种方式运行它。这是它的简化版本:
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...); }
};
我想以相同的方式存储一个可变的lambda函数
我可以存储普通的lambda,但是当我尝试存储可变的lambda时,例如:
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) { }
};
在可变的lambdas中它是正确的,但我从正常的lambdas中得到以下错误:
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。 或者也许某人有类似的类或其他解决方案来解决这个问题
答案 0 :(得分:0)
lambda表达式的计算会导致prvalue临时。在函子对象的构造函数中
template<typename functorType> Runnable(functorType& owner) { /*...*/ }
你试图将非const左值引用绑定到这样的临时值。这不起作用,无论lambda是否可变,它都无法工作。
我猜它似乎适用于不可变的lambda,因为你使用了一个没有捕获任何东西的lambda;这样的对象可以隐式转换为函数指针,因此调用实际上解析为带有函数指针的构造函数。
您可以声明构造函数代替转发引用,但您必须实际将参数复制或移动到owner
FunctorFunc
成员中。该成员不能作为引用,因为一旦对构造函数的调用完成它就可能悬空 - 如果它是lambda表达式,那么参数是暂时的,还记得吗?
这是一个修改过的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
部分的来源:非可变lambda的函数调用运算符是const
,因此可以在const functor对象上调用它。一旦lambda被声明为mutable
,那就不再是真的,所以owner
不能再为const,因此从函数声明中删除了限定符。
现在我们遇到了第二个问题:您还必须从抽象基类const
中run()
成员函数的声明中删除FuncBase
限定符。否则,您实际上在FunctorFunc
中声明了一个不同的函数,而不是覆盖基类中的函数。因此,FunctorFunc
仍然是抽象的(它仍然具有未定义的纯虚函数);这就是错误消息试图告诉你的内容。
为了避免将来遇到此问题,请使用override
specifier - 它会给您一个明确的错误消息。
编辑:构造函数Runnable(functorType&& owner)
过于通用,在某些情况下甚至可以充当复制/移动构造函数。这是使用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
将失败第二次测试,任何不具有的可调用类型都将失败正确的参数和返回类型。类型特征的最大乐趣......
代码假设您正在使用C ++ 14,否则它会更加冗长。