Reputation: 10122
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?
Upvotes: 18
Views: 9822
Reputation: 171263
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.
Upvotes: 17
Reputation: 60979
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)
.
Upvotes: 7