Why use std::bind over lambdas in C++14?
Asked Answered
P

5

80

Before C++11 I used boost::bind or boost::lambda a lot. The bind part made it into the standard library (std::bind) the other part became part of the core language (C++ lambdas) and made the use of lambdas a lot easier. Nowadays, I hardly use std::bind, since I can do almost anything with C++ lambdas. There's one valid use-case for std::bind that I can think of:

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"

The C++14 equivalent for that would be

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

Much shorter and more concise. (In C++11 this does not work yet because of the auto parameters.) Is there any other valid use case for std::bind beating the C++ lambdas alternative or is std::bind superfluous with C++14?

Priapitis answered 28/6, 2013 at 10:50 Comment(8)
I thought that lambdas should already be preferred to bind wherever that made sense.Paz
interfacing with foreign (eg. C) code?Averett
@BartekBanachewicz the question is what is that wherever.Averett
Lambdas can be inlined - binds can'tFrighten
The C++11 example doesn't even need a bind. Just use auto f = foo{};Halter
Good point. So is there any other use-case?Priapitis
@Frighten Why wouldn't bound functors be inlined ?Someday
I think that bind sometimes shows "intent" better than possible arbitrary code. Specially if you have an existing function to call. But that's pretty weak as a reason. I think there is little reason to use it in new code.Let
V
97

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.

Vizza answered 9/7, 2013 at 9:49 Comment(4)
Is there a way to move variadic template arguments into a C++14 closure? Something like: template<typename F, typename ...Args> auto make_thunk(F&& f, Args&&... args) { return [a = std::move(args)] { f(a); }Brash
Link to the talk is unfortunately brokenOfori
There is still this that lambda's can't do: codereview.stackexchange.com/questions/234887/… Although we can now use std::invoke for that. But is it just as slow?Astatine
@Brash this is actually a good use case for std::bind (see godbolt.org/z/WpZ6ZU for an example), with the caveat that any template parameters f may take need to be explicitly stated when invoking make_thunk. You should also be able to use generic lambdas in C++14 to achieve the same effect (see @BertR's comment here).Henpeck
S
9

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

Stockinet answered 15/2, 2018 at 11:46 Comment(3)
Note that you can remove the empty parenthesis in the lambda definition.Weaponeer
They are not equivalent. Your bind captures by value but your lambda by reference. You better not dangle in the latter case!Ticking
Not sometimes, but often.Ransell
F
3

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);
Forwhy answered 28/6, 2013 at 12:48 Comment(4)
Why use bind for that, when you could use mem_fn?Joseph
For convenience basically; You can use mem_fn for that. I think of bind as a "more generic" mem_fn (and now that bind has been added to the library I see no reason to use mem_fn). Similarly, "why use mem_fn when you can call the function directly"? I think a bind expression expresses intent (bind member as separated predicate) better than a lambda.Forwhy
Huh? mem_fn was added to the language at the same time as bind, are you thinking of mem_fun? Your example should be auto is_empty = std::mem_fn(&string::empty);, that's simpler, more convenient and expresses the intent more clearly.Joseph
I was thinking of mem_fun (and didn't realize mem_fun != mem_fn). The code does indeed look simpler. TIL ...Forwhy
J
1

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&).

Jone answered 30/1, 2017 at 14:6 Comment(1)
We can use std::ref and std::crefLune
W
0

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

Wilonah answered 10/8, 2018 at 16:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.