Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What the heck does std::bind(x, y) do?

Tags:

c++

c++11

stdbind

I'm stuck reading the description of std::bind in N3225, in subsection 20.8.10.1. It says the following should print 1, but I thought that bind is supposed to copy its arguments and therefor it should print 0. If one wants to refer to the passed argument, one needs to use std::ref, right?

void f(int &a) { a = 1; }

int main() {
  int a = 0;
  std::bind(f, a)();
  std::cout << a << std::endl;
}

GCC outputs 0, agreeing with what I thought things work. But N3225 says that std::bind(f, a1) shall return a call wrapper that when called by wrapper() will call INVOKE(f, v1), where v1 shall be a (the argument I passed in, in other words, using binds's incoming parameter which is a perfect forwarding parameter, std::forward<A1>(a1)).

INVOKE(f, a) is defined by 20.8.2 to f(a). So, this defines that the call to the returned call wrapper passes the original argument. What am I missing?

like image 687
Johannes Schaub - litb Avatar asked Feb 21 '11 11:02

Johannes Schaub - litb


3 Answers

It says the following should print 1

No, it does not say that.

If one wants to refer to the passed argument, one needs to use std::ref, right?

Yes.

But N3225 says that std::bind(f, a1) shall return a call wrapper that when called by wrapper() will call INVOKE(f, v1), where v1 shall be a (the argument I passed in, in other words, using binds's incoming parameter which is a perfect forwarding parameter, std::forward(a1)).

That's where you are wrong. The "bound arguments" you passed in at the bind-call are stored in form of newly created objects of type TiD which are each constructed from forward<Ti>(ti). This is made reasonably clear by saying "tid is an lvalue of type TiD constructed from std::forward<Ti>(ti)". Due to the special treatment of reference wrappers, there is an additional "transformation layer". See 20.8.10.1.2/10 which explains how vi and Vi relate to tid and TiD.

like image 188
sellibitze Avatar answered Nov 05 '22 13:11

sellibitze


It prints a 0 at present because at the point you call std::bind it doesn't know you want to pass a reference. It doesn't look at the signature of the function to see what parameter types it takes and adjust accordingly.

To make it work properly call

void f(int &a) { a = 1; }

int main() {
  int a = 0;
  std::bind(f, std::ref(a))();
  std::cout << a << std::endl;
}

C++0x is suggesting "perfect binding" but there is a huge danger with this, which could badly break existing code silently that relies on the present behaviour. Here is a very simple example.

void myCallback( const std::string& str, int i );

function< void(int) > makeCallback( const std::string & str )
{
    return bind( myCallback, str, _1 );
}

At present you can rely on bind copying the string that you pass in with str and thus the fact it will be valid come the callback's invocation.

If it "smartly" used "perfect binding" to store it as a reference, it would break situations like this.

like image 33
CashCow Avatar answered Nov 05 '22 13:11

CashCow


Wow, this is confusing beyond belief. It defines v1 as tid and it as the following (ti is the i-th perfect forwarding bind parameter, and TiD is the decayed type of that parameter - i.e an array becomes a pointer etc).

tid is an lvalue of type TiD constructed from std::forward<Ti>(ti)

Alright, I did say, this tid is std::forward<Ti>(ti) and it's an lvalue! But this is not what it really means to say. It means

tid is an lvalue of type TiD that refers to an object constructed from std::forward<Ti>(ti)

It makes much more sense now. Because what if std::forward<Ti>(ti) is actually an rvalue? The "lvalue ... constructed from ..." is meant to mean that we create a new object from "..." and make the lvalue refer to it.

like image 2
Johannes Schaub - litb Avatar answered Nov 05 '22 13:11

Johannes Schaub - litb