【问题标题】:How to make C++11 functions taking function<> parameters accept lambdas automatically如何使采用 function<> 参数的 C++11 函数自动接受 lambda
【发布时间】: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 &lt;typename F&gt; map(F f, ...) 中的泛型类型参数不包含类型信息。此外,正如我最终发现的那样,每个 lambda 都是它自己的类型。因此,类型擦除甚至不是 lambda 与其匹配的 std::function 对象之间不兼容的问题。

---更新---

关于如何使上面的地图功能起作用或如何改进它们,已经有两个答案。只是为了澄清。我的问题不是关于如何使地图工作。还有很多其他用例涉及使用 std::function 类型参数,我认为这至少可以使代码更具可读性并使类型推断变得容易。到目前为止的答案是关于如何不使用 std::function 作为参数。我的问题是关于如何使这样的函数(带有 std::function 类型参数)自动接受 lambda。

-- 更新 2 ---

作为对 cme​​ts 的回应,这里是一个实际案例示例,其中 std::function 中的类型信息可能有用。假设我们想在 OCaml (http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html) 中实现 fold_right : ('a -&gt; 'b -&gt; 'b) -&gt; 'a list -&gt; 'b -&gt; '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&lt;A(B)&gt; 的情况。在断言存在之前,产生一个 good 情况以使用推导出的std::function&lt;A(B)&gt;。好的堆栈溢出问题是实际问题。 std::function 是一个类型擦除对象:推断的类型擦除对象(几乎?)总是一个坏主意,因为您可以改为推断出 非擦除类型
  • 关于您的更新。推断类型的效果(如果它可以工作的话)是将第二个参数限制为恰好是 vector&lt;A&gt; 而不是可以转换为输入类型 f 的任何类型的向量。这通常不是一件好事。我认为“还有很多其他用例”并不明确。结果可能所有这些都像这样:你试图推断出你需要的东西,你可以用decltype得到你需要的东西,你可以接受一个通用仿函数而不是专门的function
  • @Yakk 是的,我意识到不幸的是,我们有类型擦除,在我看来,这会削弱 std::function。换个角度看你的说法,如果没有很好的情况可以很好地利用 std::function ,为什么 c++11 标准还要费心提出这个新特性?它只是应该存储可调用的东西而不需要任何练习吗?这就是我的问题的真正意义:如何或是否有可能使它有用。
  • @SteveJessop 类型推断的效果当然是推断出函数f 是一元的,取一个B 并返回一个A。此信息是 decltype 方法无法提供的。限制为vector显然是做例子的神器。

标签: c++ c++11 lambda


【解决方案1】:

您为什么首先要通过std::function&lt;...&gt; 创建动态间接寻址?只需在函数对象上进行模板化,您就可以排序:

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;});

【讨论】:

  • 可能为了最小化你使用的界面,可能是*std::begin(c)而不是c.front()。这将允许序列以外的容器。不过我不确定:由基于范围的 for 循环完成的名称查找有点神奇,我怀疑模仿它很困难。
  • @SteveJessop:我将更改代码以使用折衷方案:*c.begin(),因为我不知道如何在 using std::begin; *begin(c); 中使用 decltype()...
  • 感谢您的回答。 std::transform() 确实是另一种方式。但是,我想使用 function 类型,因为它包含完整的类型签名(类型和奇偶校验),当与其他高阶函数结合使用时,可以很容易地使用它来推理类型。这就是为什么我想出这个问题以及为什么我想从 lambda 到 std::functions 进行类型转换。两者都包含完整的类型信息。我听说 lambda 不转换为 std::function 的唯一原因是基于较小的性能成本。我的问题是如果性能不重要,如何输入转换。
  • @TingL:我理解您尝试做的事情(推断 lambda 表达式的参数类型),如上所述,我认为一般情况下这是不可能的。对于上面的特定情况,应该可以通过跳过一个函数来获取一个函数点,但只要 lambda 有一个不可能的捕获。
  • @Dietmar:“我不确定您是否可以确定通用 lambda 的参数”当然,一旦通用 lambda 到达,您将无法确定,因为这样的 lambda 确实没有参数类型:-)
【解决方案2】:

您的地图功能已损坏。不要使用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;
}

为了记录,这忽略了您地图功能的所有错误其他

【讨论】:

  • 不,如果您在主函数中使用第 6 行和第 7 行,我的地图功能没有损坏。其他两个用例是我打算实现的。我知道你的方法,它基本上说 F 可以是任何类型,并依赖调用者为 F 提供正确的类型。根本没有类型推断或安全性。所以,你的方法工作得很好。但这与我的问题无关,即修复 lambda 和 std::function. 之间的链接
  • @TingL 没有类型推断? “Broken”有点强大,但它不够通用,无法使用您想要的一组参数类型。解决方案是更通用的,这个答案证明了这一点。您可能还应该摆脱 std::vector 的显式规范。
  • 请查看更新。答案中的解决方案可能更通用。但它并没有解决问题本身。 vector 的使用只是为了说明问题。我不认为该示例是编写map 函数的最佳方式。但它确实有效,并有助于说明问题。告诉我如何以不同的方式编写它来规避问题并不能回答它。我当然知道还有其他写map的方法。
【解决方案3】:

终于想出了一个通用包装函数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 作为参数而受到批评)回答我自己的问题,这可能是我能找到的让函数类型的参数接受 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&lt;&gt; 的自动转换在 c++11 中不起作用(不幸的是,IMO)。

希望在 c++14/1y 中,当 lambda 可以正确键入或更好地进行类型推断时,情况会有所改善。

【讨论】:

  • lambdas、std::function、转换和推导的规范不太可能改变。您只是实现了我的dynamic_function_from_lambda 建议,除了手动完成工作而不是创建通用函数。
  • @Potatoswatter 是的,您可以将 std::function ctor 视为函数。从这个意义上说,我提到的类型转换运算符也是一个函数。你的建议并没有增加太多。我的答案也没有增加太多,除了我的答案表明 std::function ctor 就足够了,不需要编写这样的函数。
  • @我一直在寻找的真正答案,正如原始问题的标题所示,是找到一种方法来消除显式类型转换。从这个意义上说,包括您在内的大多数答案都建议使用 decltyperesult_of 等其他内容来替换 std::function 在我的问题中所做的事情。
  • decltyperesult_of 非常好,而且可能更有效。但是显然有一种不同的编程方式(用于类型推导),而不是使用 std::function 中的显式类型参数进行简单推导。这就是为什么我认为你的其余答案(除了类型转换可以是一个函数)并不直接适用于这个问题。
  • 只是在回复这里的第一条评论时,dynamic_function_from_lambda 函数与 function 构造函数的不同之处在于它可以执行推导并避免显式 &lt;int (int)&gt; 在您的代码中。跨度>
【解决方案4】:

我的问题是关于如何使这样的函数(带有std::function&lt;&gt; 类型参数)自动接受 lambda。

你不能。为什么你认为这是可能的? std::function 是标准库的一部分,它没有其他类类型所能提供的功能。

此外,通过人为地将解决方案空间限制为以 lambda 作为参数并以 std::function&lt;T&gt; 作为参数并推导出 T 的函数调用,没有什么可以改变的。参数与参数不匹配,并且您已任意决定禁止更改。

给定一个函数 dynamic_function_from_lambda 将任何 lambda 封装在 std::function 中,您可以在函数调用或通过推导接受 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 的全部意义在于运行时多态性,所以如果你不使用它,它只会浪费效率低下。

【讨论】:

  • 你没有抓住重点。 1) std::string 可以通过类型转换自动取const char *。这就是为什么你可以声明一个函数f(string s,...) 并用f("Hi",...) 调用它。没有什么是不可能的。
  • 2) 我看不出你的回答如何帮助 std::function 像上面的 std::string 案例一样实现相同的目标。
  • @TingL 按照设计,语言不会同时进行转换和演绎。您可以将 lambda 转换为给定的具体 std::function&lt;foo&gt; 类型,但不能将 lambda 或其他任何内容转换为 template&lt;typename T&gt; std::function&lt;T&gt;
  • 坦率地说,我看不出为什么 lambda 不能自动转换为 std::function,因为 const char* 可以转换为 std::string。可能这更多的是偶然而不是设计,因为我认为没有充分的理由禁止这种转换。到目前为止,我唯一听到的论点是存在类型擦除并且 lambda 不支持模板。这些都不是 C++ 概念的核心。听说 lambda 将支持 C++14 中的模板。
  • 关于c++不同时进行转换和演绎,我可以看到目前很多涉及模板的东西是不支持的。 typedef's 不支持模板,所以他们想出了using。是否有根本原因不能添加新功能以允许使用模板进行转换运算符?
【解决方案5】:

实际上你可以做到这一点,甚至比使用 std::function 更好(更快、更便宜)。它有一个堆分配和一个虚函数调用。它仅用于类型擦除(接受具有相同签名的任何 CALLABLE)。但是对于 lambda,您不需要这种(昂贵的)灵活性。只需使用 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);

}

【讨论】:

    【解决方案6】:

    您可以使用&amp;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;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-02
      • 2013-03-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-07-09
      相关资源
      最近更新 更多