64

When using std::bind to bind a member function, the first argument is the objects this pointer. However it works passing the object both as a pointer and not.

See for example the following program:

#include <iostream>
#include <functional>

struct foo
{
    void bar(int v) { std::cout << "foo::bar - " << v << '\n'; }
};

int main()
{
    foo my_foo;

    auto f1 = std::bind(&foo::bar, my_foo, 1);
    auto f2 = std::bind(&foo::bar, &my_foo, 2);

    f1();
    f2();
}

Both clang and GCC compiles this without complaints, and the result works for both binds:

foo::bar - 1
foo::bar - 2

I have been trying to wrap my head around the specification (section 20.8.9) but it's one of the places where it's far from clear to me.

Should only one be correct, or are both correct?

Mark Garcia
  • 17,424
  • 4
  • 58
  • 94
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 4
    Scott's "overview of the new c++" slide 216, says two ways are both right. Except if my_foo is unique_pointer, need wrap with std::ref and it doesn't work with weak pointer – billz Mar 07 '13 at 05:53
  • 4
    Scott's overview (on slide 221 in the latest deck) also says you can use `std::move` with a `unique_ptr` to transfer ownership into the callable object returned by `bind`. It doesn't work with `weak_ptr` because you can't dereference a `weak_ptr` – Jonathan Wakely Mar 07 '13 at 09:29
  • 4
    Be careful that in the f1 example, my_foo is copied. That would not work on non-copyable objects. If you delete the copy ctor of foo, this code would not compile. – Jean-Bernard Jansen Jun 18 '14 at 12:11
  • @Jean-BernardJansen Nice for mentioning the difference. – cgsdfc Feb 02 '19 at 12:27

3 Answers3

51

Both are correct. 20.8.9.1.2 forwards to 20.8.2 to describe the requirements and the effect of your call to bind. 20.8.2 is:

20.8.2 Requirements [func.require]

1 Define INVOKE(f, t1, t2, ..., tN) as follows:

(t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;

((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;

t1.*f when N == 1 and f is a pointer to member data of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;

(*t1).*f when N == 1 and f is a pointer to member data of a class T and t1 is not one of the types described in the previous item;

f(t1, t2, ..., tN) in all other cases.

The first two options allow both a reference and a pointer.

The important thing to notice here is that the wording does not limit you to plain pointers. You could use a std::shared_ptr or some other smart pointer to keep your instance alive while bound and it would still work with std::bind as t1 is dereferenced, no matter what it is (given, of course, that it's possible).

Community
  • 1
  • 1
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • 1
    Only the second (passing a pointer) method worked for me when my instance was a static variable.The first way caused each bind to act as if it was given another instance; that is, if f1 and f2 were run in succession, both bound using the first way, then the side effects of f1 on the static instance did not show when f2 was called – Mike Lui Jan 16 '16 at 01:39
  • Ah I see now that I was copying my static instance to a new instance when I didn't pass a pointer. This is what I get for not following the rule of five – Mike Lui Jan 16 '16 at 01:46
3

To add to the correct answer (that both forms are allowed).

I think of the two binding options in analogy with function argument declaration, which may be "passed by value" or "passed by reference".

In the case of f1 (aka passing my_foo "by value") the result doesn't "see" any changes made to my_foo past the binding point. This may not be desired especially if my_foo evolves. "By value" binding has an additional "cost" of (several) calls to a copy constructor.

rytis
  • 648
  • 5
  • 10
-1

There is a difference. As rytis put forward, passing by value doesn't see the changes made to my_foo. For example, in case my_foo is a class, passing by value doesn't see a change made to a member data of my_foo.

devotee
  • 127
  • 11