72

I have a question about which style is preferred: std::bind Vs lambda in C++0x. I know that they serve -somehow- different purposes but lets take an example of intersecting functionality.

Using lambda:

uniform_int<> distribution(1, 6);
mt19937 engine;
// lambda style
auto dice = [&]() { return distribution(engine); };

Using bind:

uniform_int<> distribution(1, 6);
mt19937 engine;
// bind style
auto dice = bind(distribution, engine);

Which one should we prefer? why? assuming more complex situations compared to the mentioned example. i.e. What are the advantages/disadvantages of one over the other?

Khaled Alshaya
  • 94,250
  • 39
  • 176
  • 234

8 Answers8

51

C++0x lambdas are monomorphic, while bind can be polymorphic. You cannot have something like

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

a and b must have known types. On the other hand, tr1/boost/phoenix/lambda bind allows you to do this:

struct foo
{
  typedef void result_type;

  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"

Note that the types A and B are not fixed here. Only when f is actually used these two will be deduced.

dv_
  • 1,247
  • 10
  • 13
  • Why not just declare the lambda with explicitly-typed parameters? That would be a vast improvement over the bind solution shown above. Also, if you have more complex functionality that you want to reuse, lambda is still better than bind, since it doesn't require a struct, even if you want to bind state into the functor: `template<...>foo(A a, B b, int state){ cout ... << state; } ... auto f = [](const char* a, float b){ foo(a, b, 42); };`. – Marcelo Cantos Apr 07 '11 at 03:17
  • 2
    @Marcelo Cantos: The statement to prove was that "C++0x lambdas are monomorphic", precisely because you _must_ declare the lambda with explicitly-typed parameters. – MSalters Apr 07 '11 at 09:15
  • @MSalters: The question was (roughly): "Which is better?" I'm not sure how proving that C++0x lambdas are monomorphic answers the question. – Marcelo Cantos Apr 07 '11 at 12:17
  • 3
    @Marcelo Cantos: It shows a difference between the two. AraK asked about differences and benefits/drawbacks of bind vs. lambda. – dv_ Apr 08 '11 at 10:25
  • Which part of the question is about differences, as opposed to benefits or preference? Even the title uses "vs", which suggests a contest. – Marcelo Cantos Apr 08 '11 at 12:42
  • And if you can use the same `f` with differing argument types, does the compiler give a type error? If so, then both are monomorphic. It's just that `lambda` has manifest typing, whereas `bind` uses type inference. – Thomas Eding Nov 11 '11 at 06:17
  • 1
    @trinithis: It does not give a type error, since f is a functor with a templated call operator. – dv_ Jan 30 '12 at 11:41
  • 40
    In C++14 even this difference will disappear -- a lambda could be declared as `[](auto x){}` – p12 Jan 23 '14 at 23:03
  • 2
    You don't need a `bind` here. You can just say `auto f = foo();`. – Fozi Jul 31 '14 at 15:01
30

As you said, bind and lambdas don't quite exactly aim at the same goal.

For instance, for using and composing STL algorithms, lambdas are clear winners, IMHO.

To illustrate, I remember a really funny answer, here on stack overflow, where someone asked for ideas of hex magic numbers, (like 0xDEADBEEF, 0xCAFEBABE, 0xDEADDEAD etc.) and was told that if he were a real C++ programmer he would simply have download a list of English words and use a simple one-liner of C++ :)

#include <iterator>
#include <string>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>

int main()
{
    using namespace boost::lambda;
    std::ifstream ifs("wordsEn.txt");
    std::remove_copy_if(
        std::istream_iterator<std::string>(ifs),
        std::istream_iterator<std::string>(),
        std::ostream_iterator<std::string>(std::cout, "\n"),
        bind(&std::string::size, _1) != 8u
            ||
        bind(
            static_cast<std::string::size_type (std::string::*)(const char*, std::string::size_type) const>(
                &std::string::find_first_not_of
            ),
            _1,
            "abcdef",
            0u
        ) != std::string::npos
    );
}

This snippet, in pure C++98, open the English words file, scan each word and print only those of length 8 with 'a', 'b', 'c', 'd', 'e' or 'f' letters.

Now, turn on C++0X and lambda :

#include <iterator>
#include <string>
#include <algorithm>
#include <iostream>
#include <fstream>

int main()
{
 std::ifstream ifs("wordsEn.txt");
 std::copy_if(
    std::istream_iterator<std::string>(ifs),
    std::istream_iterator<std::string>(),
    std::ostream_iterator<std::string>(std::cout, "\n"),
    [](const std::string& s)
    {
       return (s.size() == 8 && 
               s.find_first_not_of("abcdef") == std::string::npos);
    }
 );
}

This is still a bit heavy to read (mainly because of the istream_iterator business), but a lot simpler than the bind version :)

tux3
  • 7,171
  • 6
  • 39
  • 51
Arzar
  • 13,657
  • 3
  • 25
  • 25
18

The C++ 0x lamdba syntax is more readable than the bind syntax. Once you get into more than 2-3 level bind, you code becomes pretty much unreadable and difficult to maintain. I would prefer the more intuitive lambda syntax.

pranavsharma
  • 1,085
  • 2
  • 10
  • 18
8

One of the benefits of lambdas is they are way more useful when you need to add a little big of logic on top of an existing function.

With bind, you are forced to create a new function/method/functor even if the logic is only ever needed in this one place. You need to come up with an appropriate name and it can make the code less understandable as it potentially makes you split up related logic.

With lambda, you can add the new logic inside the lambda (but are not forced to if it makes sense to create a new callable).

R Samuel Klatchko
  • 74,869
  • 16
  • 134
  • 187
  • 1
    +1. I had to close a vector of FILE* in a dtor. Instead of being able to use a lambda `[](FILE* f) { if(f) fclose(f); }` I had to create a named function and use that. The function appeared in the `private` part of the class and was thus separated by many lines from the `for_each` call – KitsuneYMG Dec 19 '09 at 09:15
8

A key advantage of lambdas is they can reference member functions statically, while bind can only reference them through a pointer. Worse, at least in compilers that follow the "itanium c++ ABI" (e.g. g++ and clang++) a pointer to a member function is twice the size of a normal pointer.

So with g++ at least, if you do something like std::bind(&Thing::function, this) you get a result that is three pointers in size, two for the pointer to member function and one for the this pointer. On the other hand if you do [this](){function()} you get a result that is only one pointer in size.

The g++ implementation of std::function can store up to two pointers without dynamic memory allocation. So binding a member function to this and storing it in a std::function will result in dynamic memory allocation while using a lambda and capturing this will not.


From a comment:

A member function must be at least 2 pointers because it must store a function pointer, and this, plus at least 1 more value for meta-data such as the number of arguments. The lambda is 1 pointer because it points to this data, not because it's been magicked away.

No

A "pointer to member function" is (at least under the "itanium C++ ABI", but I suspect other compilers are similar) two pointers in size, because it stores both a pointer to the actual member function (or a vtable offset for virtual member functions) and also a "this pointer adjustment" to support multiple inheritance. Binding the this pointer to the member member function results in an object three pointers in size.

With a lambda on the other hand, every lambda has a unique type, and the information on what code to run is stored as part of the type, not part of the value. Therefore only the captures need to be stored as part of the lambda's value. At least under g++ a lambda that captures a single pointer by value has the size of a single pointer.

Neither the lambda, the pointer to member function or the result of bind store the number of parameters as part of their data. That information is stored as part of their type.

The g++ implementation of a std::function is four pointers in size, it consists of a function pointer to a "caller" function, a function pointer to a "manager" function and a data area that is two pointers in size. The "invoker" function is used when a program wants to call the callable object stored in the std::function. The manager function is called when the callable object in the std::function needs to be copied, destroyed etc.

When you construct or assign to a std::function, implementations of the invoker and manager function are generated through templating. This is what allows the std::function to store arbitrary types.

If the type you assign is able to fit in the std::function's data area then g++'s implementation (and I strongly suspect most other implementations) will store it directly in there, so dynamic memory allocation is not needed.

To demonstrate why a lambda is far better than bind in this case I wrote some small test code.

struct widget
{
    void foo();
    std::function<void()> bar();  
    std::function<void()> baz();  
};

void widget::foo() {
    printf("%p",this);
}

std::function<void()> widget::bar() {
    return [this](){foo();};
}

std::function<void()> widget::baz() {
    return std::bind(&widget::foo, this);
}

I fed this into godbolt using the "armv7-a clang trunk" option with -O2 and -fno-rtti and looked at the resulting assembler. I have manually separated out the assembler for bar and baz. Lets first look at the assembler for bar.

widget::bar():
        ldr     r2, .LCPI1_0
        str     r1, [r0]
        ldr     r1, .LCPI1_1
        str     r1, [r0, #8]
        str     r2, [r0, #12]
        bx      lr
.LCPI1_0:
        .long   std::_Function_handler<void (), widget::bar()::$_0>::_M_invoke(std::_Any_data const&)
.LCPI1_1:
        .long   std::_Function_base::_Base_manager<widget::bar()::$_0>::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation)
std::_Function_handler<void (), widget::bar()::$_0>::_M_invoke(std::_Any_data const&):
        ldr     r1, [r0]
        ldr     r0, .LCPI3_0
        b       printf
.LCPI3_0:
        .long   .L.str
std::_Function_base::_Base_manager<widget::bar()::$_0>::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation):
        cmp     r2, #2
        beq     .LBB4_2
        cmp     r2, #1
        streq   r1, [r0]
        mov     r0, #0
        bx      lr
.LBB4_2:
        ldr     r1, [r1]
        str     r1, [r0]
        mov     r0, #0
        bx      lr

We see, that bar itself is very simple, it's just filling out the std::function object with the value of the this pointer and with pointers to the caller and manager functions. The "invoker" and "manager" functions are also pretty simple, there is no dynamic memory allocation in sight and the compiler has inlined foo into the "invoker" function.

Now lets look at the assembler for baz:

widget::baz():
        push    {r4, r5, r6, lr}
        mov     r6, #0
        mov     r5, r0
        mov     r4, r1
        str     r6, [r0, #8]
        mov     r0, #12
        bl      operator new(unsigned int)
        ldr     r1, .LCPI2_0
        str     r4, [r0, #8]
        str     r0, [r5]
        stm     r0, {r1, r6}
        ldr     r1, .LCPI2_1
        ldr     r0, .LCPI2_2
        str     r0, [r5, #8]
        str     r1, [r5, #12]
        pop     {r4, r5, r6, lr}
        bx      lr
.LCPI2_0:
        .long   widget::foo()
.LCPI2_1:
        .long   std::_Function_handler<void (), std::_Bind<void (widget::*(widget*))()> >::_M_invoke(std::_Any_data const&)
.LCPI2_2:
        .long   std::_Function_base::_Base_manager<std::_Bind<void (widget::*(widget*))()> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation)
std::_Function_handler<void (), std::_Bind<void (widget::*(widget*))()> >::_M_invoke(std::_Any_data const&):
        ldr     r0, [r0]
        ldm     r0, {r1, r2}
        ldr     r0, [r0, #8]
        tst     r2, #1
        add     r0, r0, r2, asr #1
        ldrne   r2, [r0]
        ldrne   r1, [r2, r1]
        bx      r1
std::_Function_base::_Base_manager<std::_Bind<void (widget::*(widget*))()> >::_M_manager(std::_Any_data&, std::_Any_data const&, std::_Manager_operation):
        push    {r4, r5, r11, lr}
        mov     r4, r0
        cmp     r2, #3
        beq     .LBB6_3
        mov     r5, r1
        cmp     r2, #2
        beq     .LBB6_5
        cmp     r2, #1
        ldreq   r0, [r5]
        streq   r0, [r4]
        b       .LBB6_6
.LBB6_3:
        ldr     r0, [r4]
        cmp     r0, #0
        beq     .LBB6_6
        bl      operator delete(void*)
        b       .LBB6_6
.LBB6_5:
        mov     r0, #12
        bl      operator new(unsigned int)
        ldr     r1, [r5]
        ldm     r1, {r2, r3}
        ldr     r1, [r1, #8]
        str     r0, [r4]
        stm     r0, {r2, r3}
        str     r1, [r0, #8]
.LBB6_6:
        mov     r0, #0
        pop     {r4, r5, r11, lr}
        bx      lr

We see it's worse than the code for bar in almost every respect. The code for baz itself is now over twice as long and includes dynamic memory allocation.

The invoker function can no longer inline foo or even call it directly, instead it must go through the whole rigmarole of calling a pointer to member function.

The manager function is also substantially more complex and involves dynamic memory allocation.

plugwash
  • 9,724
  • 2
  • 38
  • 51
  • A member function must be at least 2 pointers because it must store a function pointer, and `this`, plus at least 1 more value for meta-data such as the number of arguments. The lambda is 1 pointer because it points to this data, not because it's been magicked away. – c z Jun 01 '21 at 11:55
  • I'm curious about whether this still applies, and whether/how it applies to the new `std::bind_front` function. Trying it out in godbolt now, none appear to allocate memory dynamically (which is what I'd expect, even for the old `std::bind`). – Arthur Tacca Aug 25 '23 at 10:08
  • Do you have an example of bind_front? note that it's not bind itself that does dynamic memory allocation, it's assigning a std::function to an object that is too big to store inline. – plugwash Aug 26 '23 at 06:58
3

I think it's more a matter of taste. People that quickly grasp new technologies, or are familiar with functional programming will probably prefer lambda syntax, while more conservative programmers will definitively prefer bind, as it is more in par with the traditional C++ syntax.

Such a decision should be made in coordination with the people that will be working with the code, probably through a majority vote.

Which doesn't change the fact however, that lambda syntax is much more powerful and cleaner.

Kornel Kisielewicz
  • 55,802
  • 15
  • 111
  • 149
  • 3
    People in a team keep changing. Code readability is very important esp. for future maintenance programmers. Hence we should go with whichever solution that offers more readability and between lamdba and bind, lamda definitely takes the cake. – pranavsharma Dec 18 '09 at 22:25
2

C++0x lambdas essentially replace bind. There is nothing you can bind that you can't recreate a trivial wrapper lambda to achieve the same. std::tr1::bind will go the way of std::bind1st, etc once lambda support is wide spread. Which is good, because for some reason most programmers have a hard time getting their head around bind.

Terry Mahaffey
  • 11,775
  • 1
  • 35
  • 44
  • 5
    While this answer wasn't correct at the time it was posted, it is accurate for C++14. The link in the comment above confirms this now. – Drew Dormann May 24 '16 at 04:29
0

As of C++20 (released since this question was asked in 2009!) there is a third choice: std::bind_front. (C++23 also has std::bind_back.) Use like this:

auto dice = std::bind_front(distribution, engine);

It can also be used for member functions:

struct MyType {
    void myFn();

    auto getBound() {
        return std::bind_front(&MyType::myFn, this);
    }
};

It doesn't suffer from some of the problems of std:bind: it has a simpler implementation because it doesn't allow reorganising the order of parameters, so it's more likely to be optimised by the compiler, and it's move-aware so forwards its arguments properly.

However, it does still potentially generate a little more code than a lambda, because the function pointer / member function pointer is stored as a data member of the resulting value rather than part of its type. I wouldn't expect this to make a big difference to performance unless you're in the absolute tightest of loops though.

Arthur Tacca
  • 8,833
  • 2
  • 31
  • 49