user2845840
user2845840

Reputation: 396

C++ using capturing lambda inside virtual method

I tried to use the following C++ code, but it fails to compile with a no matching function for call to ... error for the call to z inside bar::s when compiling with g++ 4.9.2 and --std=c++11:

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

The code compiles if I remove the virtual in front of the method definition and also if I remove the captured scale from the the lambda. If understand the issue correctly, it works like this: When the method is not virtual, the lambda with capture is used via the operator () of its associated type. When the method is virtual, that operator can't be used because it can't be called via dynamic dispatch, only via a normal method call. But the lambda can't be converted to a function pointer either, because that conversion is only available for captureless lambdas. And I assume that z is called via dynamic dispatch if s is virtual, even though z is not virtual. Is that analysis correct?

What I don't get is why z can't be called via a normal method call - it is not virtual, after all. What I also don't get is why removing the template parameters and replacing all Ts with floats makes the compilation fail for both the virtual and the non-virtual case (removing the captured scale still allows successful compilation).

In any case, is there a simple and somewhat efficient way of fixing this? I would like to use lambdas with captures (or something similar) in both virtual and non-virtual methods of template classes. I know of std::function, but it's quite heavyweight. And captureless lambdas are quite a bit less powerful than lambdas with captures, so they probably won't be able to solve my problems.

PS: If you want to know what z is supposed to be doing: It is a combination of what would be zipWith followed by foldl in Haskell (or somewhat like mapreduce with two input lists if you don't know Haskell) and can be used for most unary and binary operations using foos. In my original implementation, init was not of type T, and instead used as type an additional template parameter that was removed here for simplicity's sake.

Upvotes: 1

Views: 460

Answers (2)

Miles Budnek
Miles Budnek

Reputation: 30579

Use a template.

virtual here doesn't actually have anything to do with your problem. The problem is that raw function pointers can't carry any state with them, and so lambdas with captures can't be converted to function pointers.

Instead of a raw function pointer, you should make z a template that accepts any type as its second argument:

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

Now that z accepts your lambda by its actual type, its state can be carried along, and everything works.

Upvotes: 2

user2845840
user2845840

Reputation: 396

As Raymond Chen pointed out, the virtuality is indeed a red herring.

For the time being, I decided to go with this ugly workaround, but I won't accept my answer, because it is not a real solution:

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

This compiles and allows me to use z2 with up to 2 "pseudo-captures" of the right type, with or without virtuality and even with the additional template parameter that I left out in the question. Unneeded captures can simply be set to 0 and ignored.

Upvotes: 0

Related Questions