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

时间:2014-12-28 20:21:30

标签: c++ lambda

我有自己的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。 或者也许某人有类似的类或其他解决方案来解决这个问题

1 个答案:

答案 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::forwardstd::move。如果事情不清楚,请问我。该解决方案处理rvalue和lvalue可调用对象; rvalues通常是lambdas,而lvalues可能是你单独构建的其他类型的可调用对象。

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

现在我们遇到了第二个问题:您还必须从抽象基类construn()成员函数的声明中删除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,否则它会更加冗长。