Reputation: 396
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 T
s with float
s 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 foo
s. 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
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;
}
Now that z
accepts your lambda by its actual type, its state can be carried along, and everything works.
Upvotes: 2
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