为什么在C++14中使用std::bind而不是lambda表达式?

78

在C++11之前,我经常使用boost::bindboost::lambda。其中的bind部分已成为标准库的一部分(std::bind),而另一个部分则成为核心语言(C++ lambda)的一部分,并且大大简化了Lambda表达式的使用。现在,我几乎不再使用std::bind,因为我可以用C++ Lambda表达式实现几乎任何功能。我能想到只有一个有效的std::bind用例:

struct foo
{
  template < typename A, typename B >
  void operator()(A a, B b)
  {
    cout << a << ' ' << b;
  }
};

auto f = bind(foo(), _1, _2);
f( "test", 1.2f ); // will print "test 1.2"

对应C++14的代码为:

auto f = []( auto a, auto b ){ cout << a << ' ' << b; }
f( "test", 1.2f ); // will print "test 1.2"

更加简短和简洁。(在C++11中,由于自动参数的原因,这种方法尚不可行。)除了使用std::bind而不是C++ lambda之外,还有其他有效的用例吗?或者说,在C++14中,std::bind是否已经变得多余了?


8
我认为在合适的情况下,应该优先使用lambda而不是bind - Bartek Banachewicz
与外部(例如C)代码进行接口? - didierc
3
问题是,“无论何处”的“何物”是什么。 - didierc
Lambda函数可以内联 - 绑定不能。 - doctorlove
13
C++11示例甚至不需要使用bind。只需使用auto f = foo {};即可。 - aschepler
显示剩余3条评论
5个回答

95

Scott Meyers在演讲中提到了这个问题,以下是我记得的:

在C++14中,bind所能做的任何有用操作都可以通过lambda表达式完成。

然而,在C++11中,有一些事情是lambda表达式无法完成的:

  1. 在创建lambda表达式时,无法移动变量进行捕获。变量始终作为左值被捕获。对于bind,您可以编写:

    auto f1 = std::bind(f, 42, _1, std::move(v));
    
  2. 表达式无法捕获,只有标识符可以。对于绑定,您可以编写:

  3. auto f1 = std::bind(f, 42, _1, a + b);
    
  4. 函数对象重载参数。这已经在问题中提到了。

  5. 无法完美地转发参数

在C++14中,所有这些都是可能的。

  1. 移动示例:

  2. auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  3. 表达式示例:

  4. auto f1 = [sum = a + b](auto arg) { f(42, arg, sum); };
    
  5. 请看问题

  6. 完美转发:您可以编写

    auto f1 = [=](auto&& arg) { f(42, std::forward<decltype(arg)>(arg)); };
    

bind 的一些缺点:

  • bind 是按名称绑定的,因此如果您有多个具有相同名称(重载函数)的函数,则 bind 不知道要使用哪个函数。以下示例将无法编译,而 lambda 将不会有问题:

void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
  • 使用绑定函数时,不太可能进行内联
  • 另一方面,lambda表达式理论上可能会生成比绑定函数更多的模板代码。因为对于每个lambda表达式,您都会得到一个唯一的类型。而对于绑定函数,仅在具有不同参数类型和不同函数时才会出现(我猜在实践中,很少会使用相同的参数和函数进行多次绑定)。

    Jonathan Wakely在他的答案中提到的另一个原因是不使用绑定函数的一个更多的原因。我看不出为什么要默默忽略参数。


    有没有一种方法可以将可变参数模板参数移动到C++14闭包中?像这样:template<typename F, typename ...Args> auto make_thunk(F&& f, Args&&... args) { return [a = std::move(args)] { f(a); } - Zendel
    很遗憾,该演讲的链接已经失效。 - Ayxan Haqverdili
    Lambda函数仍然有无法实现的功能:https://codereview.stackexchange.com/questions/234887/more-ergonomic-convenient-stl-algorithm-calls-sort-order-member-field-ext虽然我们现在可以使用std::invoke来解决这个问题。但它是否同样缓慢呢? - Oliver Schönrock
    @Zendel 这实际上是使用 std::bind 的一个很好的用例(请参见 https://godbolt.org/z/WpZ6ZU 以获取示例),但需要注意的是,在调用 make_thunk 时,任何模板参数 f 都需要明确声明。您还可以在 C++14 中使用通用 lambda 表达式来实现相同的效果(请参见 @BertR 在此处的评论:https://stackoverflow.com/a/17365046)。 - Breakthrough

    8
    有时候,更少的代码才是更好的选择。考虑以下这段代码:
    bool check(int arg1, int arg2, int arg3)
    {
      return ....;
    }
    

    那么

    wait(std::bind(check,a,b,c));
    

    vs lambda

    wait([&](){return check(a,b,c);});
    

    我认为在这里使用bind函数比lambda更易读,lambda看起来像https://en.wikipedia.org/wiki/Brainfuck

    3
    请注意,您可以在 lambda 定义中删除空括号。 - Hugal31
    2
    它们不是等价的。你的绑定通过值捕获,但你的lambda通过引用捕获。在后一种情况下最好不要悬挂! - underscore_d

    3

    对我而言,std::bind 的一个有效用途是明确表示我正在将成员函数用作谓词。也就是说,如果我只是调用成员函数,那么就使用 bind。如果我对参数进行额外处理(除了调用成员函数),那么就使用 lambda:

    using namespace std;
    auto is_empty = bind(&string::empty, placeholders::_1); // bind = just map member
    vector<string> strings;
    auto first_empty = any_of(strings.begin(), strings.end(), is_empty);
    
    auto print_non_empty = [](const string& s) {            // lambda = more than member
        if(s.empty())                // more than calling empty
            std::cout << "[EMPTY]";  // more than calling empty
        else                         // more than calling empty
            std::cout << s;          // more than calling empty
    };
    vector<string> strings;
    for_each(strings.begin(), strings.end(), print_non_empty);
    

    4
    为什么要使用bind,而不是使用mem_fn - Jonathan Wakely
    1
    基本上,您可以使用 mem_fn 来方便地进行操作。我认为 bind 是一个“更通用”的 mem_fn(现在已经将 bind 添加到库中,因此没有理由使用 mem_fn)。同样,“为什么要使用 mem_fn 当您可以直接调用函数?”我认为 bind 表达了意图(将成员绑定为分离的谓词)比 lambda 更好。 - utnapistim
    2
    什么?mem_fnbind同时被添加到语言中,你是在想mem_fun吗?你的例子应该是auto is_empty = std::mem_fn(&string::empty);,这更简单、更方便,也更清晰地表达了意图。 - Jonathan Wakely
    我曾经考虑过mem_fun(并没有意识到mem_fun!= mem_fn)。代码看起来确实更简单。今天我学到了... - utnapistim

    1

    另一个区别是,绑定(bind)的参数必须被复制或移动,而lambda可以使用被引用捕获的变量。请参见下面的示例:

    #include <iostream>
    #include <memory>
    
    void p(const int& i) {
        std::cout << i << '\n';
    }
    
    int main()
    {
        std::unique_ptr<int> f = std::make_unique<int>(3);
    
        // Direct
        p(*f);
    
        // Lambda ( ownership of f can stay in main )
        auto lp = [&f](){p(*f);};
        lp();
    
        // Bind ( does not compile - the arguments to bind are copied or moved)
        auto bp = std::bind(p, *f, std::placeholders::_1);
        bp();
    }
    

    不确定是否有可能在不更改 void p(const int&) 的签名的情况下绕过此问题使用上面的绑定。


    2
    我们可以使用 std::refstd::cref - kyb

    0

    仅仅是扩展@BertR的评论this answer,使其变得可测试,尽管我承认我无法使用std::forward<>找到解决方案。

    #include <string>
    #include <functional>
    using namespace std::string_literals;
    
    struct F {
        bool        operator()(char c, int  i) { return c == i;  };
        std::string operator()(char c, char d) { return ""s + d; };
    };
    
    void test() {
        { // using std::bind
            auto f = std::bind(F(), 'a', std::placeholders::_1);
            auto b = f(1);
            auto s = f('b');
        }
        { // using lambda with parameter pack
            auto x = [](auto... args) { return F()('a', args...); };
            auto b = x(1);
            auto s = x('b');
        }
    }
    

    Compiler Explorer上进行测试


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接