C ++在虚拟方法中使用捕获lambda

时间:2019-03-11 00:36:37

标签: c++ c++11 lambda virtual-method dynamic-dispatch

我尝试使用以下C ++代码,但使用g ++ 4.9.2和{{编译时,在no matching function for call to ...内对z的调用bar::s时,编译失败,并出现--std=c++11错误1}}:

template <class T>
class foo {
    public:
        virtual void s( T scale , const foo<T>& rhs ) {
        }
};

template <class T>
class bar : public foo<T> {
    public:
        T z( T init , T (*f)( T a , T& x , const T& y ) , const foo<T>& rhs ) {
        }
        void s( T scale , const foo<T>& rhs ) {
            this->z( ((T)0) , [=]( T a, T& l, const T& r ) {return ((l+=scale*r)?((T)0):((T)0));} , rhs );
        }
};

int main () {
    bar<float>* a = new bar<float>();
    foo<float>* b = new foo<float>();
    return 0;
}

如果我删除方法定义前面的virtual,也要从lambda中删除捕获的scale,则代码会编译。如果正确理解了该问题,则其工作方式如下:如果该方法不是虚拟的,则通过与其关联类型的operator ()使用带有捕获的lambda。如果该方法是虚拟的,则不能使用该运算符,因为它不能通过动态分派(仅通过常规方法调用)来调用。但是lambda也不能转换为函数指针,因为该转换仅适用于无捕获的lambda。而且我假设即使z不是虚拟的,如果s是虚拟的,也会通过动态调度调用z。这种分析正确吗?

我不明白的是为什么不能通过常规方法调用来调用z-毕竟它不是虚拟的。我还没有得到的是为什么删除模板参数并用T替换所有float会导致虚拟和非虚拟情况下的编译失败(删除捕获的{{1 }}仍允许成功编译。

无论如何,是否有一种简单且有效的方法来解决此问题?我想在模板类的虚拟和非虚拟方法中使用带有捕获(或类似内容)的lambda。 。我知道scale,但是它很重。而且,无捕获Lambda的功能要比具有捕获的Lambda强大得多,因此它们可能无法解决我的问题。

PS:如果您想知道std::function应该做什么:它是Haskell中z后跟zipWith的组合(或有点像mapreduce与两个输入列表(如果您不知道Haskell,则可以使用foldl进行大多数一元和二进制运算)。在我最初的实现中,foo的类型不是init,而是用作附加模板参数的类型,为简单起见,此处将其删除。

2 个答案:

答案 0 :(得分:2)

使用模板。

virtual实际上与您的问题无关。问题在于原始函数指针不能携带任何状态,因此带有捕获的lambda不能转换为函数指针。

您应该使z成为可以接受任何类型作为其第二个参数的模板,而不是原始的函数指针:

template <class T>
class foo {
    public:
        virtual void s( T scale , const foo<T>& rhs ) {
        }
};

template <class T>
class bar : public foo<T> {
    public:
        template <typename F>
        T z( T init , F&& f, const foo<T>& rhs ) {
            f(/*some args*/);
        }
        void s( T scale , const foo<T>& rhs ) {
            this->z( ((T)0) , [=]( T a, T& l, const T& r ) {return ((l+=scale*r)?((T)0):((T)0));} , rhs );
        }
};

int main () {
    bar<float>* a = new bar<float>();
    foo<float>* b = new foo<float>();
    return 0;
}

Live Demo

现在z按其实际类型接受您的lambda,可以携带其状态,并且一切正常。

答案 1 :(得分:0)

正如雷蒙德·陈(Raymond Chen)所指出的那样,虚拟性确实是一条红鲱鱼。

暂时,我决定采用这种丑陋的解决方法,但由于它不是真正的解决方案,所以我不接受我的回答。

template <class T>
class foo {
    public:
        virtual void s( T scale , const foo<T>& rhs ) {
        }
};

template <class T>
class bar : public foo<T> {
    public:
        T z( T init , T (*f)( T a , T& x , const T& y ) , const foo<T>& rhs ) {
        }
        template <class U>
        U z2( U init , T capture_0 , T capture_1 , U (*f)( T capture_0 , T capture_1 , U a , T& x , const T& y ) , const foo<T>& rhs ) {
            return init;
        }
        void s( T scale , const foo<T>& rhs ) {
            this->z2<T>( ((T)0) , scale , ((T)0) , []( T c0 , T c1 , T a, T& l, const T& r ) {return ((l+=c0*r)?((T)0):((T)0));} , rhs );
            //this->z( ((T)0) , [=]( T a, T& l, const T& r ) {return ((l+=scale*r)?((T)0):((T)0));} , rhs );
        }
};

int main () {
    bar<float>* a = new bar<float>();
    foo<float>* b = new foo<float>();
    a->s(0,*b);
    return 0;
}

这可以编译并允许我使用z2,最多可以使用2个正确类型的“伪捕获”,带有或不带有虚拟性,甚至可以使用我在问题中遗漏的其他模板参数。不需要的捕获可以简单地设置为0并忽略。