如何使C ++ 11函数具有函数<>参数自动接受lambdas

时间:2013-12-21 19:51:14

标签: c++ c++11 lambda

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。也许这太希望了。我希望这澄清了这个问题。

6 个答案:

答案 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;
}
相关问题