C++ 在虚方法中使用捕获 lambda
C++ using capturing lambda inside virtual method
我尝试使用以下 C++ 代码,但在使用 g++ 4.9.2 和--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;
}
如果我删除方法定义前面的 virtual
并且如果我从 lambda 中删除捕获的 scale
,代码将编译。如果正确理解问题,它是这样工作的:当方法不是虚拟时,通过其关联类型的 operator ()
使用带有捕获的 lambda。当方法是虚方法时,不能使用该运算符,因为它不能通过动态调度调用,只能通过普通方法调用。但是 lambda 也不能转换为函数指针,因为该转换仅适用于无捕获 lambda。我假设 z
如果 s
是虚拟的,则通过动态调度调用,即使 z
不是虚拟的。分析正确吗?
我不明白的是为什么 z
不能通过普通方法调用来调用 - 毕竟它不是虚拟的。我也不明白为什么删除模板参数并将所有 T
s 替换为 float
s 会使虚拟和非虚拟情况下的编译失败(删除捕获的 scale
仍然允许编译成功)。
无论如何,有没有一种简单且有点有效的方法来解决这个问题?我想在虚拟和非捕获中使用 lambdas(或类似的东西)模板的虚拟方法 类。我知道 std::function
,但它相当重量级。而且无捕获 lambda 的功能远不如带捕获的 lambda,因此它们可能无法解决我的问题。
PS:如果您想知道 z
应该做什么:它是 zipWith
后跟 foldl
在 Haskell(或者有点像带有两个输入列表的 mapreduce,如果您不知道 Haskell)并且可以用于大多数使用 foo
的一元和二元运算。在我最初的实现中,init
不是 T
类型,而是用作类型的附加模板参数,为简单起见,此处已将其删除。
正如雷蒙德陈指出的那样,虚拟性确实是一个转移注意力的问题。
目前,我决定采用这种丑陋的解决方法,但我不会接受我的回答,因为它不是真正的解决方案:
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 个正确类型的 "pseudo-captures" 一起使用,有或没有虚拟性,甚至还有我在问题中遗漏的附加模板参数。不需要的捕获可以简单地设置为 0 并忽略。
使用模板。
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;
}
现在 z
通过实际类型接受您的 lambda,它的状态可以继续,一切正常。
我尝试使用以下 C++ 代码,但在使用 g++ 4.9.2 和--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;
}
如果我删除方法定义前面的 virtual
并且如果我从 lambda 中删除捕获的 scale
,代码将编译。如果正确理解问题,它是这样工作的:当方法不是虚拟时,通过其关联类型的 operator ()
使用带有捕获的 lambda。当方法是虚方法时,不能使用该运算符,因为它不能通过动态调度调用,只能通过普通方法调用。但是 lambda 也不能转换为函数指针,因为该转换仅适用于无捕获 lambda。我假设 z
如果 s
是虚拟的,则通过动态调度调用,即使 z
不是虚拟的。分析正确吗?
我不明白的是为什么 z
不能通过普通方法调用来调用 - 毕竟它不是虚拟的。我也不明白为什么删除模板参数并将所有 T
s 替换为 float
s 会使虚拟和非虚拟情况下的编译失败(删除捕获的 scale
仍然允许编译成功)。
无论如何,有没有一种简单且有点有效的方法来解决这个问题?我想在虚拟和非捕获中使用 lambdas(或类似的东西)模板的虚拟方法 类。我知道 std::function
,但它相当重量级。而且无捕获 lambda 的功能远不如带捕获的 lambda,因此它们可能无法解决我的问题。
PS:如果您想知道 z
应该做什么:它是 zipWith
后跟 foldl
在 Haskell(或者有点像带有两个输入列表的 mapreduce,如果您不知道 Haskell)并且可以用于大多数使用 foo
的一元和二元运算。在我最初的实现中,init
不是 T
类型,而是用作类型的附加模板参数,为简单起见,此处已将其删除。
正如雷蒙德陈指出的那样,虚拟性确实是一个转移注意力的问题。
目前,我决定采用这种丑陋的解决方法,但我不会接受我的回答,因为它不是真正的解决方案:
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 个正确类型的 "pseudo-captures" 一起使用,有或没有虚拟性,甚至还有我在问题中遗漏的附加模板参数。不需要的捕获可以简单地设置为 0 并忽略。
使用模板。
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;
}
现在 z
通过实际类型接受您的 lambda,它的状态可以继续,一切正常。