Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't std::bind account for function arity?

Tags:

c++

c++11

If I have this simple case:

struct Foo 
{
    void bar();
    void baz(int );
};

It makes sense that this would compile:

Foo foo;
auto f = std::bind(&Foo::bar, &foo);

But why would bind be designed in such a way that this compiles:

auto g = std::bind(&Foo::baz, &foo);

I can call f, but I cannot ever call g. Why even make that compile? What is the rationale behind requiring me to have to do:

auto g2 = std::bind(&Foo::baz, &foo, std::placeholders::_1);

I can understand using the placeholders if you want to mess with which arguments get passed and in what order, but why not just have the default pass all the arguments in the right order without having to specify it?

like image 989
Barry Avatar asked Oct 10 '14 11:10

Barry


People also ask

What does std :: bind do in C++?

std::bind is a Standard Function Objects that acts as a Functional Adaptor i.e. it takes a function as input and returns a new function Object as an output with with one or more of the arguments of passed function bound or rearranged.

What is _1 in boost :: bind?

_1 is a placeholder. Boost. Bind defines placeholders from _1 to _9 . These placeholders tell boost::bind() to return a function object that expects as many parameters as the placeholder with the greatest number.

What is the return type of std :: bind?

std::bind return type The return type of std::bind holds a member object of type std::decay<F>::type constructed from std::forward<F>(f), and one object per each of args... , of type std::decay<Arg_i>::type, similarly constructed from std::forward<Arg_i>(arg_i).


1 Answers

But why would bind be designed in such a way that this compiles:
auto g = std::bind(&Foo::baz, &foo);
I can call f, but I cannot ever call g. Why even make that compile?

The Boost.Bind FAQ says that Boost.Bind will usually diagnose such errors at "bind time" (i.e. on the line where you call bind). However the standard doesn't require that for std::bind, instead it has the following in the Requires element for std::bind:

INVOKE (fd, w1, w2, ..., wN) (20.9.2) shall be a valid expression for some values w1, w2, ..., wN, where N == sizeof...(bound_args).

This means your code violates the function's precondition, which results in undefined behaviour. The standard library implementation is not obliged to check for precondition violations, that's your job. The library isn't forbidden to check them either, so it would be conforming for an implementation to reject it, as Boost.Bind does. I would make a request to your library vendor asking them to diagnose invalid bind expressions where it is possible to do so, as a "Quality of Implementation" enhancement. (Edit: I made libstdc++'s bind do this, starting with GCC 5.)

why not just have the default pass all the arguments in the right order without having to specify it?

I can think of two reasons.

Firstly, the behaviour of the call wrapper created by bind is to drop arguments that do not correspond to a placeholder, so you can call x(1, 2, 3) and have it ignore all the arguments and call foo.bar(). This is part of a general pattern where you can wrap an N-arity function using bind to create a call wrapper with a completely different arity that might add arguments, remove them, fix some to specific bound values etc. It would be impossible to have x(1, 2, 3) drop all arguments if the default behaviour when no placeholders are used in the bind expression was to forward all the arguments.

Secondly, it's more consistent to always require you to be explicit about which arguments you want passed in which order. In general it would only make sense to pass all the invocation arguments when there are no bound arguments, otherwise how should bind know whether to pass the invocation arguments before or after the bound arguments?

e.g. given

struct X {
  void f(int, int) { }
} x;
auto h = bind(&X::f, &x, 1);
h(2);

Should the call to h(2) result in x.f(1, 2) or x.f(2, 1)? Since the correct behaviour when there are bound arguments is not obvious, automatically forwarding all arguments when there were no placeholders used only really makes sense when there are no bound arguments (because then there's no question of whether the bound arguments should come first or last), which is a fairly special case. Changing a significant feature of the API to work with that special case would be of questionable value, especially when it makes the x(1, 2, 3) -> foo.bar() case impossible to achieve.

An alternative solution is to continue requiring users to be explicit about what they want, but provide an explicit way to say "just forward everything", as proposed by Tomasz Kamiński in N4171 which will be discussed at the C++ committee meeting next week. The _all placeholder solves the problem of deciding whether the invocation arguments should come before or after the bound arguments, because you can explicitly say whether you want bind(f, arg1, arg2, std::placeholders::_all) or bind(f, std::placeholders::_all, arg1, arg2) or even bind(f, arg1, std::placeholders::_all, arg2)

like image 90
Jonathan Wakely Avatar answered Sep 23 '22 18:09

Jonathan Wakely