18

The following:

#include <functional>

struct Foo 
{
    void bar1() {}
    void bar2(int) {}
    void bar3(int, int) {}
    void bar4(int, int, int) {}
};


int main()
{
    Foo foo;

    auto f1 = std::bind(&Foo::bar1, &foo);
    auto f2 = std::bind(&Foo::bar2, &foo);
    auto f3 = std::bind(&Foo::bar3, &foo);
    auto f4 = std::bind(&Foo::bar4, &foo);

    f1(1, 2, 3, 4); // success
    f2(1, 2, 3);    // error
    f3(1, 2);       // error
    f4(1);          // error    

    return 0;
}

f1(1, 2, 3, 4) compiles and executes bar1(). f2(1, 2, 3), doesn't compile, f3(1, 2) doesn't compile (yet bar3 has the correct prototype) and f4(1) doesn't compile. The error I get with Visual Studio 2013 for those 3 cases is

"class does not define an 'operator()' or a user defined conversion operator to a pointer-to-function or reference-to-function that takes appropriate number of arguments"

I have limited understanding of templates and the Standard Library, but this doesn't seem to make any logical sense to me. Is there a reasonably simple explanation?

Robinson
  • 9,666
  • 16
  • 71
  • 115
  • My personal solution to `std::bind`: it's C++11 and you have lambdas, forget about it. `auto f1 = [&]() { foo.bar1() };` – Matteo Italia Apr 13 '15 at 16:02
  • 9
    You probably forgot to put the placeholders (_1, _2, _3) in `f2`, `f3`, `f4`. @Matteo Italia `bind` is sometimes more concise than a lambda. – sbabbi Apr 13 '15 at 16:03
  • 3
    OK, so f1(1, 2, 3, 4) which binds to bar1() doesn't need any placeholders. But because operator() doesn't "check" arity, bar1() gets called and (1, 2, 3, 4) gets discarded by the template magic, without an error. – Robinson Apr 13 '15 at 16:06
  • 3
    void main is just as wrong. – Columbo Apr 13 '15 at 16:12
  • 1
    @sbabbi: I don't think that a (rarely) more terse notation justify the way worse syntax and the horrible error messages whenever you mess up something. `boost::bind` was just a workaround to make the functional parts of the STL *vaguely* usable in C++03, I don't see why we should continue to use such a kludge now that we finally gained the real thing (lambdas and, most importantly, closures) at core language level. As with `std::auto_ptr` and other workarounds for language shortcomings, it's time to forget that it ever existed. – Matteo Italia Apr 13 '15 at 16:20
  • How do you get rid of bind in this context? Wrapping with a lambda and capturing "foo"? What happens if you want to store f1, f2, f3, f4 into an array for later use? How do you call the lambda with the appropriate "this" later on? – Robinson Apr 13 '15 at 16:27
  • 3
    You can't store all bind-results in one array either without type erasure. If you use `std::function`, that works just fine with lambdas. – Columbo Apr 13 '15 at 16:28
  • It sounds too good to be true. I'll give it a try in a sec. – Robinson Apr 13 '15 at 16:33
  • 1
    Upvoted for title – Jake Jan 24 '18 at 10:59

2 Answers2

17

To pass arguments to the target you need to provide those arguments in the bind expression, or leave them unbound by adding placeholders to the bind expression and then you must call the function with arguments to be substituted for the placeholders.

You can call bar1 without placeholders because it doesn't take any arguments, so you don't need to pass anything to it. The arguments you pass to f1 are just ignored, because there are no unbound arguments, i.e. no placeholders that need replacing.

The other functions require arguments, so you must either bind arguments at "bind time" e.g.

auto f2a = std::bind(&Foo::bar2, &foo, 1);
f2a();

or leave the arguments unbound and provide them when you invoke the callable object:

auto f2b = std::bind(&Foo::bar2, &foo, std::placeholders::_1);
f2b(1);

Note that GCC 5 now has static assertions to catch this kind of error. If the arity of the target function can be determined and the bind expression doesn't have either a bound argument or placeholder for every parameter of the target function then it says:

/usr/local/gcc-head/include/c++/5.0.0/functional:1426:7: error: static assertion failed: Wrong number of arguments for pointer-to-member

What you wrote is equivalent to this, using lambdas instead of bind:

Foo foo;

auto f1 = [&foo](...) { return foo.bar1(); };
auto f2 = [&foo](...) { return foo.bar2(); };
auto f3 = [&foo](...) { return foo.bar3(); };
auto f4 = [&foo](...) { return foo.bar4(); };

f1(1, 2, 3, 4); // success
f2(1, 2, 3);    // error
f3(1, 2);       // error
f4(1);          // error   

i.e. you define functors that will take any arguments, but ignore them, and then call member functions of Foo, but not necessarily with the right arguments.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
7

It seems you have a misunderstanding of what bind is supposed to do.
The arguments you pass to the result of bind are "substituted" into the placeholders _1, _2, and so on. The arguments that do not correspond to a placeholder are, well, unused and therefore ignored. Since you aren't using any placeholders in your bind expressions, the internal call to bar1 is always equivalent to foo.bar1() - thus it's independent from the arguments you pass to the result of bind.

It now becomes clear that the call to bar4 is equivalent to foo.bar4(), but bar4 expects four arguments, so that's nonsense. You can fix that by writing

using namespace std::placeholders;
auto f4 = std::bind(&Foo::bar4, &foo, _1, _2, _3, _4);

Now if you provide four arguments, those will be passed on correctly and the internal call becomes equivalent to e.g. foo.bar4(1, 2, 3, 4).

Columbo
  • 60,038
  • 8
  • 155
  • 203