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.