将多种类型的 lambda 函数存储在一种类型的变量中

Storing multiple type lambda functions in one type of variable

本文关键字:类型 一种 变量 存储 种类 lambda 函数      更新时间:2023-10-16

我有自己的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) { }
    };

它在可变的 lambda 中是正确的,但我从普通 lambda 中得到以下错误:

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); }
                                 ^
                                                                                 ^

主要问题是我如何修改这个类以接受 lambda。或者也许有人对这个问题有类似的类或其他解决方案

lambda 表达式的计算结果是临时值。在函子对象的构造函数中

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

您正在尝试将非常量左值引用绑定到此类临时引用。这是行不通的,无论 lambda 是否可变,它都不起作用。

我猜它似乎适用于不可变的 lambda,因为你使用了没有捕获任何东西的 lambda;这样的对象隐式可转换为函数指针,因此调用实际上解析为采用函数指针的构造函数。

您可以声明构造函数以改为采用转发引用,但您必须实际将参数复制或移动到FunctorFuncowner成员中。该成员不能是引用,因为一旦对构造函数的调用完成,它可能会悬而未决 - 如果它是一个 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::forwardstd::move 。如果有什么不清楚的地方,请问我。该解决方案处理右值和左值可调用对象;右值通常是 lambda,而左值可能是您单独构造的一些其他类型的可调用对象。

请注意,我还必须从 run() 成员函数的声明中删除 const 限定符。这就是mutable部分的用武之地:不可变 lambda 的函数调用运算符是 const ,因此可以在 const 函子对象上调用它。一旦 lambda 被声明为 mutable ,那就不再是真的了,所以owner不能再是常量,因此从函数声明中删除了限定符。

现在我们到了第二个问题:您还必须从抽象基类 FuncBase 中的 run() 成员函数的声明中删除 const 限定符。否则,您实际上是在FunctorFunc中声明一个不同的函数,而不是覆盖基数中的函数。正因为如此,FunctorFunc仍然是抽象的(它仍然有一个未定义的纯虚函数);这就是错误消息试图告诉您的。

为避免将来遇到此问题,请使用override说明符 - 它会为您提供明确的错误消息。


编辑:构造函数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 s 将无法通过第一个测试,具有不同模板参数的 Runnable s 将在第二个测试中失败,任何没有正确参数和返回类型的可调用类型也是如此。类型特征的最大乐趣...

代码假设您使用的是 C++14,否则它会(甚至)更冗长。

相关文章: