Bind Vs Lambda?
Asked Answered
R

8

75

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?

Retread answered 18/12, 2009 at 21:58 Comment(3)
are there any performance differences? speed, memory consumption, heap usage?Beefburger
@Caspin I don't really know if there is a difference in performance/memory consumption between these two tools :)Retread
btw: the two versions are not equivalent because bind copies arguments. Alternative: bind(ref(distribution),ref(engine))Reflexive
P
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 :)

Pentyl answered 19/12, 2009 at 1:20 Comment(3)
Although the two pieces of code don't do the same thing, I got your point of view very clearly :)Retread
the lambda should be: [](const std::string& s) -> boolMember
@Beh Tou Cheh I think the type should be deduced if lambda consists of return <expression>; only(as Thomas did).Retread
S
52

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.

Smog answered 3/1, 2011 at 2:38 Comment(9)
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); };.Karlykarlyn
@Marcelo Cantos: The statement to prove was that "C++0x lambdas are monomorphic", precisely because you must declare the lambda with explicitly-typed parameters.Bundelkhand
@MSalters: The question was (roughly): "Which is better?" I'm not sure how proving that C++0x lambdas are monomorphic answers the question.Karlykarlyn
@Marcelo Cantos: It shows a difference between the two. AraK asked about differences and benefits/drawbacks of bind vs. lambda.Smog
Which part of the question is about differences, as opposed to benefits or preference? Even the title uses "vs", which suggests a contest.Karlykarlyn
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.Warty
@trinithis: It does not give a type error, since f is a functor with a templated call operator.Smog
In C++14 even this difference will disappear -- a lambda could be declared as [](auto x){}Frederic
You don't need a bind here. You can just say auto f = foo();.Angele
P
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 :)

Pentyl answered 19/12, 2009 at 1:20 Comment(3)
Although the two pieces of code don't do the same thing, I got your point of view very clearly :)Retread
the lambda should be: [](const std::string& s) -> boolMember
@Beh Tou Cheh I think the type should be deduced if lambda consists of return <expression>; only(as Thomas did).Retread
V
19

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.

Volk answered 18/12, 2009 at 22:0 Comment(3)
Disagree. [this](){Type* this_too = this; run([this_too](){this_too->f();});} is neither readable nor intuitive.Xerosere
Admittedly I think new lines would help your counterexample. New lines would not help much with bind.Warty
Personal opinion anyway. Should be a comment, not an answer.Brigantine
P
11

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.

Pentagon answered 18/11, 2019 at 14:49 Comment(3)
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.Brigantine
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).Dingbat
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.Pentagon
M
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).

Mombasa answered 19/12, 2009 at 1:28 Comment(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 callGallia
L
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.

Lp answered 18/12, 2009 at 22:15 Comment(1)
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.Volk
P
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.

Ponderable answered 24/12, 2009 at 20:18 Comment(1)
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.Gardner
D
2

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.

Dingbat answered 25/8, 2023 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.