【发布时间】:2013-12-21 19:51:14
【问题描述】:
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 存储到自动变量然后使用它,类型信息仍然丢失,可能是由于类型擦除和性能损失较小的原因(有人告诉我:why do lambda functions in c++11 not have function<> types?)。
有效的方法是将 lambda 存储在 std:function 类型变量中并使用该变量。这是相当不方便的,并且有点违背在 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 类型参数,我认为这至少可以使代码更具可读性并使类型推断变得容易。到目前为止的答案是关于如何不使用 std::function 作为参数。我的问题是关于如何使这样的函数(带有 std::function 类型参数)自动接受 lambda。
-- 更新 2 ---
作为对 cmets 的回应,这里是一个实际案例示例,其中 std::function 中的类型信息可能有用。假设我们想在 OCaml (http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html) 中实现 fold_right : ('a -> 'b -> 'b) -> 'a list -> 'b -> 'b 的 C++ 等价物。
使用 std::function,可以做到
//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 的正确方法是什么?从可读性的角度来看,我想代码的读者只能通过INTERPRETING函数体中的实现来弄清楚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);
抛开效率问题不谈,上面的代码很不方便,因为 std::function 不能接受 lambda。所以,
fold_right([](int x, int y) -> int { return x + y; },{1,2,3},0);
目前在 C++11 中无法使用。我的问题特别是关于是否可以使上面定义的fold_right 之类的函数直接接受 lambda。也许希望太大了。我希望这可以澄清问题。
【问题讨论】:
-
您的所有示例都是不适合使用
std::function<A(B)>的情况。在断言存在之前,产生一个 good 情况以使用推导出的std::function<A(B)>。好的堆栈溢出问题是实际问题。std::function是一个类型擦除对象:推断的类型擦除对象(几乎?)总是一个坏主意,因为您可以改为推断出 非擦除类型。 -
关于您的更新。推断类型的效果(如果它可以工作的话)是将第二个参数限制为恰好是
vector<A>而不是可以转换为输入类型f的任何类型的向量。这通常不是一件好事。我认为“还有很多其他用例”并不明确。结果可能所有这些都像这样:你试图推断出你需要的东西,你可以用decltype得到你需要的东西,你可以接受一个通用仿函数而不是专门的function。 -
@Yakk 是的,我意识到不幸的是,我们有类型擦除,在我看来,这会削弱 std::function。换个角度看你的说法,如果没有很好的情况可以很好地利用 std::function ,为什么 c++11 标准还要费心提出这个新特性?它只是应该存储可调用的东西而不需要任何练习吗?这就是我的问题的真正意义:如何或是否有可能使它有用。
-
@SteveJessop 类型推断的效果当然是推断出函数
f是一元的,取一个B并返回一个A。此信息是 decltype 方法无法提供的。限制为vector显然是做例子的神器。