C ++ 11同时具有lambda和std :: function<>但不幸的是,它们有不同的类型。 一个结果是,人们无法直接在高阶函数中使用lambda,例如lisp中的map。例如,在以下代码中
#include <vector>
#include <functional>
using namespace std;
template <typename A,typename B>
vector<B> map(std::function<B (A)> f, vector<A> arr) {
vector<B> res;
for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
return res;
}
int main () {
vector<int> a = {1,2,3};
map([](int x) -> int { return x;},a); //not OK
auto id_l = [](int x) -> int { return x;};
map(id_l,a); //not OK;
function<int (int)> id_f = id_l;
map(id_f,a); //OK
return 0;
}
,直接在main()的第2行使用lambda将不起作用。 g++ -std=c++11 testfunc.cpp
返回`... testfunc.cpp:14:37:注意:'main():: __ lambda0'不是从'std :: function'派生的。
C ++ 11类型推理也失败了,你可以看到,如果将lambda存储到auto变量然后使用它,类型信息仍然会丢失,可能是由于类型擦除和性能损失小的原因(正如我被告知:why do lambda functions in c++11 not have function<> types?)。
将lambda存储在std:function&lt;&gt;中的作用是什么?键入变量并使用该变量。这是相当不方便的,并且在C ++ 11中失败了在函数式编程中使用lambda的目的。例如,人们无法使用bind或flip之类的东西来操纵lambda,而是必须先将lambda存储到变量中。
我的问题是,是否有可能(以及如何)克服这个问题并使main()的第2行合法,例如通过覆盖一些类型转换运算符? (当然,这意味着我不关心使用/不使用类型擦除所涉及的性能损失。)
提前感谢。
---编辑---
为了澄清,我使用std::function
而不是函数参数的泛型类型参数的原因是std::function
具有确切的类型信息,而template <typename F> map(F f, ...)
中的泛型类型参数包含没有类型信息。而且,正如我最终想到的那样,每个lambda都是它自己的类型。因此,类型擦除甚至不是lambda与其匹配的std::function
对象之间不兼容的问题。
--- ---更新
关于如何使地图功能正常工作或如何改进它们,已经有两个答案。只是为了澄清。我的问题不是关于如何使地图工作。还有很多其他用例涉及使用std :: function&lt;&gt;类型参数,我认为至少可以使代码更具可读性并使类型推理变得容易。到目前为止的答案是关于如何不使用std :: function&lt;&gt;作为参数。我的问题是关于如何使这样的函数(使用std :: function&lt;&gt; typed parameters)自动接受lambda。
- 更新2 ---
在回应评论时,这是一个实例的例子,其中std :: function&lt;&gt;中的类型信息可能很有用。假设我们想在OCaml(http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html)中实现与fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b
等效的C ++。
使用std :: function&lt;&gt;,可以做
//approach#1
template <typename A,typename B>
B fold_right(std::function<B (A, B)> f, vector<A> arr, B b) {
...
}
从上面可以清楚地了解f
是什么,以及它可以或不可以采取什么。也许,人们也可以使用
//approach#2
template <typename A,typename B, typename F>
auto fold_right2(F f, vector<A> arr, B b) -> decltype(f(???)) {
...
}
但是,当你试图找出decltype
中的内容时,这会变得有点丑陋。此外,f
究竟采取了什么,以及使用f
的正确方法是什么?从可读性的角度来看,我想代码的读者只能通过解释函数体中的实现来找出f(函数或标量)和f的签名。
这就是我不喜欢的,这就是我的问题所在。如何使方法#1方便地工作。例如,如果f
表示添加了两个数字,那么如果您首先创建一个函数对象,则方法#1会起作用:
std::function<int (int, int)> add = [](int x, int y) -> int { return x + y; }
fold_right(add,{1,2,3},0);
抛开效率问题,上面的代码不方便BECAUSE std :: function不能接受lambda的。所以,
fold_right([](int x, int y) -> int { return x + y; },{1,2,3},0);
目前无法在C ++ 11中使用。我的问题是关于是否可以使上面定义的fold_right
之类的函数直接接受lambda。也许这太希望了。我希望这澄清了这个问题。
答案 0 :(得分:7)
为什么要首先通过std::function<...>
创建动态间接?只是对函数对象进行模板化,然后对其进行排序:
template <typename A, typename F>
auto map(F f, std::vector<A> arr) -> std::vector<decltype(f(arr[0]))> {
std::vector<decltype(f(arr[0]))> res;
for (int i=0; i<arr.size(); ++i)
res.push_back(f(arr[i]));
return res;
}
事实上,确实没有必要确定容器类型,你可能也希望通过[const
]引用传递它:
template <typename C, typename F>
auto map(F f, C const& c) -> std::vector<decltype(f(*c.begin()))> {
std::vector<decltype(f(*c.begin()))> res;
for (auto const& value: c)
res.push_back(f(value));
return res;
}
最后,请注意标准C ++库已经作为“地图”功能。它碰巧拼写为std::transform()
,并且有一个更符合C ++通用方法的接口:
std::vector<int> result;
std::transform(a.begin(), a.end(), std::back_inserter(result),
[](int x){ return x;});
答案 1 :(得分:5)
您的地图功能已损坏。除非您不能使用模板,否则请勿使用std::function
;在这种情况下,你肯定可以。您不需要B
作为模板参数,因为decltype
可以将它提供给您,并且您根本不需要参数类型实际上是std::function
。
template <typename A, typename F> auto map(F f, vector<A> arr) -> std::vector<decltype(f(arr.front())> {
std::vector<decltype(f(arr.front())> res;
for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
return res;
}
对于记录,这忽略了地图功能错误的所有 else 。
答案 2 :(得分:5)
最后找出了一个通用的包装函数make_function
(在当前的c ++ 11中),用于将任何lambda转换为带有类型推导的相应std::function
对象。现在而不是使用ctor:
map(function<int (int)>( [](int x) -> int { return x;} ), {1,2,3});
需要两次提供相同的类型信息,以下简洁形式
map(make_function([](int x) -> int { return x;}),a); //now OK
代码如下:
#include <vector>
#include <functional>
using namespace std;
template <typename T>
struct function_traits
: public function_traits<decltype(&T::operator())>
{};
template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const> {
typedef function<ReturnType (Args...)> f_type;
};
template <typename L>
typename function_traits<L>::f_type make_function(L l){
return (typename function_traits<L>::f_type)(l);
}
template <typename A,typename B>
vector<B> map(std::function<B (A)> f, vector<A> arr) {
vector<B> res;
for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
return res;
}
int main () {
vector<int> a = {1,2,3};
map(make_function([](int x) -> int { return x;}),a); //now OK
return 0;
}
- 原始答案 -
在经过几周的搜索之后回答我自己的问题(并且因为使用std :: function&lt;&gt;作为参数而受到惩罚),我可以找到具有函数&lt;&gt; -typed参数的最佳方法接受lambda的(在c ++ 11中)只是通过显式转换:
map((function<int (int)>) ([](int x) -> int { return x;} ), {1,2,3});
或者使用ctor:
map(function<int (int)>( [](int x) -> int { return x;} ), {1,2,3});
为了进行比较,如果您有一个使用std :: string的函数(例如void ff(string s) {...}
),它可以自动执行const char*
。 (ff("Hi")
会起作用)。从lambda到std::function<>
的自动转换在c ++ 11中同样起作用(不幸的是,IMO)。
希望,当lambdas可以正确输入或更好地进行类型推导时,c ++ 14 / 1y中的事情会有所改善。
答案 3 :(得分:3)
我的问题是关于如何使这样的函数(使用
std::function<>
类型参数)自动接受lambda。
你做不到。为什么你认为这是可能的? std::function
是标准库的一部分,除了其他类类型之外,它没有任何功能。
此外,通过人为地将解空间限制为以lambda作为参数的函数调用,并std::function<T>
作为推导T
的参数,没有什么可能改变。参数与参数不匹配,并且您已经任意决定禁止更改。
给定一个函数dynamic_function_from_lambda
来封装std::function
中的任何lambda,你可以在函数调用中或通过演绎接受lambda对象的函数体中显式执行转换。
备选方案A:
map( dynamic_function_from_lambda( []( int a ){ return a + 1 } ), v );
备选方案B:
template< typename F, typename V >
std::vector< typename std::result_of< F( typename V::value_type ) >::type >
map( F f, V v )
{ return map( dynamic_function_from_lambda( f ), std::move( v ) ); }
std::function
的重点是运行时多态,所以如果你不使用它,那就太浪费了。
答案 4 :(得分:0)
实际上你可以做到甚至比使用std :: function更好(更快,更便宜)。它有一个堆alloc和一个虚函数调用。它仅用于类型擦除(接受具有相同签名的任何CALLABLE)。但对于lambdas,你不需要这种(昂贵的)灵活性。只需使用lambda包装类
#include <iostream>
#include <functional>
#include <vector>
template <typename T, typename ... Args>
struct lambda_wrapper : public lambda_wrapper<decltype(&T::operator())(Args...)> {
using RetType = decltype(&T::operator())(Args...);
};
template <typename L>
struct lambda_wrapper<L> {
private:
L lambda;
public:
lambda_wrapper(const L & obj) : lambda(obj) {}
template<typename... Args>
typename std::result_of<L(Args...)>::type operator()(Args... a) {
return this->lambda.operator()(std::forward<Args>(a)...);
}
template<typename... Args> typename
std::result_of<const L(Args...)>::type operator()(Args... a) const {
return this->lambda.operator()(std::forward<Args>(a)...);
}
};
template <typename T>
auto make_lambda_wrapper(T&&t) {
return lambda_wrapper<T>(std::forward<T>(t));
}
template <typename A, typename F>
auto map(F f, std::vector<A> arr)
{
std::vector < decltype(f(arr.front())) > res;
for (int i=0;i<arr.size();i++) res.push_back(f(arr[i]));
return res;
}
int main(int argc, char ** argv) {
std::vector<int> a = {1,2,3};
map(make_lambda_wrapper([](int a) -> int { return a*2;} ),a);
}
答案 5 :(得分:0)
您可以使用 &LambdaT::operator()
获取 lambda 参数,如下所示:
template <typename R, typename LambdaT, typename... Args>
auto _LambdaToStdFunction(LambdaT lambda, R (LambdaT::*)(Args...) const) {
return std::function<R(Args...)>(lambda);
}
template <typename LambdaT>
auto LambdaToStdFunction(LambdaT &&lambda) {
return _LambdaToStdFunction(std::forward<LambdaT>(lambda),
&LambdaT::operator());
}
注意:常量限定符
测试示例ideone:
#include <functional>
#include <iostream>
#include <vector>
template <typename LambdaT, typename R, typename... Args>
auto _LambdaToStdFunction(LambdaT lambda, R (LambdaT::*)(Args...) const) {
return std::function<R(Args...)>(lambda);
}
template <typename LambdaT>
auto LambdaToStdFunction(LambdaT &&lambda) {
return _LambdaToStdFunction(std::forward<LambdaT>(lambda),
&LambdaT::operator());
}
template <typename A, typename B>
std::vector<B> map(std::function<B(A)> f, std::vector<A> arr) {
std::vector<B> res;
for (int i = 0; i < arr.size(); i++) res.push_back(f(arr[i]));
return res;
}
int main() {
std::vector<int> a = {1, 2, 3};
auto f = LambdaToStdFunction([](int x) -> int {
std::cout << x << std::endl;
return x;
});
map(LambdaToStdFunction([](int x) -> int {
std::cout << x << std::endl;
return x;
}),
a); // now OK
return 0;
}