Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can std::bind take too many arguments? [duplicate]

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 411
Barry Avatar asked Oct 10 '14 11:10

Barry


People also ask

How do you fix too many arguments in a function?

Solution. So to fix the error “You've entered too many arguments for this function” you need to go through the content of the cell, character-by-character, to ensure that there are no syntax errors. In other words, you need to check whether all opened brackets are closed and all commas are properly in place.

Is std :: bind deprecated?

Yes: std::bind should be replaced by lambda For almost all cases, std::bind should be replaced by a lambda expression. It's idiomatic, and results in better code. There is almost no reason post C++11 to use std::bind .

What does std:: Bind returns?

std::bind. Returns a function object based on fn , but with its arguments bound to args . Each argument may either be bound to a value or be a placeholder: - If bound to a value, calling the returned function object will always use that value as argument.

What is std:: Bind 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. Suppose We have a function to add two numbers i.e. int add(int first, int second) {


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 118
Jonathan Wakely Avatar answered Sep 17 '22 16:09

Jonathan Wakely