How to bind function to an object by reference?

texasbruce picture texasbruce · Oct 3, 2014 · Viewed 10.3k times · Source

I have the following code to bind a member function to an instance of the class:

class Foo {
public:
    int i;
    void test() {
        std::cout << i << std::endl;
    }
};

int main() {
    Foo f;
    f.i = 100;
    auto func = std::bind(&Foo::test, std::forward<Foo>(f));
    f.i = 1000;
    func();
}

But the std::bind statement does not bind to f by reference. Calling func prints "100" instead of "1000" which is what I want.

However, If I change the statement to take a pointer it works.

auto func = std::bind(&Foo::test, &f);

But this is passing f by pointer by my understanding, and as I thought std::bind takes an r-value reference Arg&& (as shown here) how can this work?

Can someone please explain this?

Answer

Snps picture Snps · Oct 4, 2014

Note on using std::forward

First of all, std::forward is meant to be used for perfect forwarding, i.e. to forward the reference type (l-value or r-value).

If you pass an l-value reference to std::forward that is what is returned, and likewise if an r-value reference is passed then an r-value is returned. This works as opposed to std::move that will always return an r-value reference. Also remember that named r-value references are l-value references.

/* Function that takes r-value reference. */
void func(my_type&& t) {
    // 't' is named and thus is an l-value reference here.

    // Pass 't' as lvalue reference.
    some_other_func(t);
    // Pass 't' as rvalue reference (forwards rvalueness).
    some_other_func(std::forward<my_type>(t));
    // 'std::move' should be used instead as we know 't' is always an rvalue.
    // e.g. some_other_func(std::move(t));
}

Also you should never use std::forward or std::move on an object that you afterwards need to access some state from. Objects that are moved from are put in an unspecified but valid state, which basically means that you cant do anything with them except destroy or reassign a state to them.

Arguments passed by reference to std::bind?

The function std::bind will always copy or move its arguments. I could not find an appropriate citation from the standard but en.cppreference.com says:

"The arguments to bind are copied or moved, and are never passed by reference unless wrapped in std::ref or std::cref."

This means that if you pass the argument(s) as an l-value reference then it is copy constructed, and if you pass it as an r-value reference then it is move constructed. In either way the argument(s) will never be passed as references.

To circumvent this you can e.g. use std::ref as a copyable reference wrapper that will internally keep a reference to the variable at the call site.

auto func = std::bind(&Foo::test, std::ref(f));

Or you could simply pass a pointer to f as you suggest.

auto func = std::bind(&Foo::test, &f);

What happens then is that std::bind will take an r-value reference to the temporary pointer returned from calling the address-of operator on f. This pointer will be copied (as pointers can't be moved) into the bound wrapper object returned from std::bind. Even though the pointer itself is copied, it will still point to the object at the call site and you will achieve the reference semantics you wanted.

Alternatively use a lambda that captures f by reference and calls the function Foo::test. This is IMHO the recommended approach as lambdas are more versatile and powerful than std::bind expressions in the general case.

Foo f;
f.i = 100;
auto func = [&f] { f.test(); };
f.i = 1000;
func(); // 1000

Note: for a good explanation of when to use std::forward see this excellent video by Scott Meyers.