为什么在 C + + 14中使用 std: : bind over lambdas?

在 C + + 11之前,我经常使用 boost::bind或者 boost::lambdabind部分使其成为标准库(std::bind) ,另一部分成为核心语言(C + + lambdas)的一部分,使得 lambdas 的使用变得更加容易。现在,我很少使用 std::bind,因为我几乎可以用 C + + lambdas 做任何事情。我能想到的 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 + + lambdas 的替代方案还有其他有效的用例吗? 或者 std::bind对于 C + + 14来说是多余的吗?

49313 次浏览

For me, a valid use for std::bind is to make it clear that I'm using a member function as a predicate. That is, if all I do is call a member function, it's bind. If I do extra stuff with the argument (besides calling a memeber function), it's a 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);

Scott Meyers gave a talk about this. This is what I remember:

In C++14 there is nothing useful bind can do that can't also be done with lambdas.

In C++11 however there are some things that can't be done with lambdas:

  1. You can't move the variables while capturing when creating the lambdas. Variables are always captured as lvalues. For bind you can write:

    auto f1 = std::bind(f, 42, _1, std::move(v));
    
  2. Expressions can't be captured, only identifiers can. For bind you can write:

    auto f1 = std::bind(f, 42, _1, a + b);
    
  3. Overloading arguments for function objects. This was already mentioned in the question.

  4. Impossible to perfect-forward arguments

In C++14 all of these possible.

  1. Move example:

    auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  2. Expression example:

    auto f1 = [sum = a + b](auto arg) { f(42, arg, sum); };
    
  3. See question

  4. Perfect forwarding: You can write

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

Some disadvantages of bind:

  • Bind binds by name and as a result if you have multiple functions with the same name (overloaded functions) bind doesn't know which one to use. The following example won't compile, while lambdas wouldn't have a problem with it:

    void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
    
  • When using bind functions are less likely to be inlined

On the other hand lambdas might theoretically generate more template code than bind. Since for each lambda you get a unique type. For bind it is only when you have different argument types and a different function (I guess that in practice however it doesn't happen very often that you bind several time with the same arguments and function).

What Jonathan Wakely mentioned in his answer is actually one more reason not to use bind. I can't see why you would want to silently ignore arguments.

Another difference is that arguments to bind must be copied or moved, while a lambda can use variables captured by reference. See example below:

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

Not sure if it's possible to workaround the issue to use bind above without changing the signature of void p(const int&).

Sometimes it is just less code. Consider this:

bool check(int arg1, int arg2, int arg3)
{
return ....;
}

Then

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

vs lambda

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

I think that bind is easier to read here compared to the lambda which looks like a https://en.wikipedia.org/wiki/Brainfuck

Just expanding @BertR's comment to this answer to something testable, though I confess I couldn't quite get a solution using std::forward<> to work.

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

Test at Compiler Explorer