Why Do Objects Returned from Bind Ignore Extra Arguments

Why do objects returned from bind ignore extra arguments?

Ignoring extra arguments is a lot simpler to implement, and can actually be useful.

In a typical implementation e.g. libstdc++ (g++), the approach taken is to collect the operator() arguments into a tuple and then let the std::placeholder bind arguments extract them as required. Enforcing argument count would require counting the number of used placeholders, which would be pretty complicated. Note that the bind callable can be a functor with multiple or templated operator() call patterns, so the bind object operator() can't be generated with a single "correct" signature.

Also note that you can write:

std::bind(&foo, std::placeholders::_1, std::placeholders::_3);

i.e. explicitly ignoring the second argument to the bind object. If bind enforced its argument count you would need an additional way to specify that e.g. a fourth argument was also to be ignored.

As for usefulness, consider binding a member signal handler to a signal:

sig.connect(std::bind(&C::on_sig, this, param, std::placeholders::_1));

If sig has extra unwanted emission parameters, then they are simply ignored by the bind object; otherwise, binding the same handler to multiple signals would require writing multiple forwarding wrappers for no real purpose.

Is std::bind ignoring superfluous arguments standard-conforming?

Yes, for std::bind, the superfluous arguments will be discarded.

If some of the arguments that are supplied in the call to g() are not matched by any placeholders stored in g, the unused arguments are evaluated and discarded.

Why std::bind can be assigned to argument-mismatched std::function?

That is correct behavior.

std::bind needs this looseness to fit its own specification.

Consider std::placeholders, which is used to mark parameters that are passed through to the bound function.

using std::placeholders;
std::function<void(int)> f2 = std::bind( F, _1 );
// Parameter 1 is passed to ^^
// the bound function.

f2(7); // The int 7 is passed on to F

Similarly, there is _2 for the second parameter, _3 for the third, and so on.

That brings up an interesting question. How should this function object behave?

auto f3 = std::bind( F, _3 );

As you might imagine, it follows its own promise to pass the third parameter to F. Which means it does nothing to the first two parameters.

f3(10, 20, 30); // The int 30 is passed on to F. The rest?  Ignored.

So this is expected behavior, and possibly the only "feature" that std::bind holds over lambdas, even in C++14 and C++17.

The object produced by std::bind is designed to accept and ignore any extraneous parameters.

std::bind assigned to std::function

Note: this answer assumes that you have read Why do objects returned from bind ignore extra arguments?

The object returned by std::bind is not a std::function; it is an object of an unspecified type that differs from std::function in a number of ways; most importantly for your case, it ignores any extra arguments passed to it.

If you use placeholders up to _2 (for example) in your bind expression, the returned object can be called with any number of arguments as long as that number is at least 2.

  • What is auto doing that the compiler doesn't complain when I call jj_2a(5, 6)? That function has all its parameters bound.

You are passing extra arguments; those extra arguments are ignored.

  • But if I don't use auto, I get the behaviour I expect (compile error with arguments). So clearly function is not at all what auto decided.

Correct; bind does not return a std::function, it returns a callable object of unspecified type that can (depending on signature) be used to construct a std::function.

  • If I bind the first argument and not the second (jj_3), then calling with two arguments works (but drops the wrong argument, according to my mental model) while calling with one argument (which I think should work) doesn't compile.

If you use std::placeholders::_2, you must pass at least 2 arguments. std::placeholders::_2 picks the second argument passed to the object returned from bind.

  • Using std::functional for jj_3_f says "no viable conversion", though the error message isn't so far helping me.

If you use std::placeholders::_2, you must pass at least 2 arguments; the constructor of std::function checks this.

Is template type of a std::function detached by std::bind an undefined behavior?

From cppreference.com:

If some of the arguments that are supplied in the call to [the bound object] are not matched by any placeholders stored in [the bound object], the unused arguments are evaluated and discarded.

The bound object from the question has no placeholders. So the nullptr in

return filter(nullptr);

is evaluated and discarded, and the result is the same as if

return filter();

was called. If we interpret the binding, this is the same as

return filter_entity(ptr);

(or would be if ptr was in scope). If you want to use the nullptr in the return statement, you would need to use std::placeholders::_1 in your std::bind.


A followup question to this might be Why do objects returned from bind ignore extra arguments?

c++: How to write a std::bind-like object that checks for superfulous parameters?

Note: Restricting the number of arguments breaks a feature of binders that is illustrated here.

My solution is based on counting the passed placeholders determining the greatest placeholder used. Thanks to Xeo for pointing out this error.

#include <functional>
#include <type_traits>
#include <utility>

template<class T, class U>
constexpr auto c_max(T&& t, U&& u)
-> typename std::remove_reference<decltype( t > u ? t : u )>::type
{ return t > u ? t : u; }

template<class...>
struct max_placeholder : std::integral_constant<int, 0> {};

template<class T, class... Rest>
struct max_placeholder<T, Rest...>
: std::integral_constant<int, c_max(std::is_placeholder<T>::value,
max_placeholder<Rest...>::value)>
{};

This lays the burden to correctly count the number on the user of the binder. For some bound Callables such as function pointers, it is possible to deduce the number of arguments (this also allows automatically supplying the required amount of placeholders). Once you have fixed the number of arguments either way, it's easy to write a wrapper that stores a binder and provides an operator() template that checks the number of arguments:

template<class T, int N>
struct strict_binder
{
T binder;

template<class... Args>
auto operator()(Args&&... args)
-> decltype( binder(std::forward<Args>(args)...) )
{
static_assert(sizeof...(args) == N, "wrong number of arguments");
return binder(std::forward<Args>(args)...);
}
};

It is also possible to produce a substitution failure instead of an error.

As the strict_binder is a binder, you can express this concept via a partial specialization:

namespace std
{
template<class T, int N>
struct is_bind_expression< strict_binder<T, N> >
: public true_type
{};
}

All that remains is to write a function template that produces strict_binders. Here's a version that's similar to std::bind:

template<class F, class... Args>
auto strict_bind(F&& f, Args&&... args)
-> strict_binder<
typename std::decay<
decltype( std::bind(std::forward<F>(f), std::forward<Args>(args)...) )
>::type,
max_placeholder<typename std::remove_reference<Args>::type...>::value
>
{
return { std::bind(std::forward<F>(f), std::forward<Args>(args)...) };
}

Essentially, the return type is a

strict_binder<decltype(std::bind(f, args...)), count_placeholders<Args...>::value>

That is, the strict_binder stores the resulting type of std::bind.


You can also write an apply-like function that calls the bound function when no placeholders have been passed:

template<int N, class F, class... Args>
auto strict_bind_or_call(std::integral_constant<int, N>, F&& f, Args&&... args)
-> strict_binder<
typename std::decay<
decltype( std::bind(std::forward<F>(f), std::forward<Args>(args)...) )
>::type,
N
>
{
return { std::bind( std::forward<F>(f), std::forward<Args>(args)... ) };
}

template<class F, class... Args>
auto strict_bind_or_call(std::integral_constant<int, 0>, F&& f, Args&&... args)
-> decltype( std::bind( std::forward<F>(f), std::forward<Args>(args)... ) () )
{
return std::bind( std::forward<F>(f), std::forward<Args>(args)... ) ();
}

template<class F, class... Args>
auto strict_bind(F&& f, Args&&... args)
-> decltype( strict_bind_or_call( std::integral_constant<int, max_placeholder<typename std::remove_reference<Args>::type...>::value>{},
std::forward<F>(f), std::forward<Args>(args)... ) )
{
using max_placeholder_here =
max_placeholder<typename std::remove_reference<Args>::type...>;

return strict_bind_or_call( max_placeholder_here{},
std::forward<F>(f), std::forward<Args>(args)... );
}

This uses tag dispatch to either return a binder or the result of calling the function.
I gave up formatting this properly, you might want to introduce alias templates in a detail namespace.

Note the decltype( std::bind(..) () ) in the second overload of strict_bind_or_call is a simple way to reproduce the semantics of INVOKE / bind; I can't just write f(args...) because f might be a member function.


Usage example:

#include <iostream>

void foo(int p0, int p1)
{ std::cout << "[" << p0 << ", " << p1 << "]\n"; }

int main()
{
auto f0 = strict_bind(foo, std::placeholders::_1, 42);
f0(1);

strict_bind(foo, 1, 2);
}

Can I use boost::bind to store an unrelated object?

Yes, you can do this by binding the extra parameters. I often did that with asio, e.g. in order to keep buffers or other state alive during the async operation.

You can also access these extra arguments from the handler afterwards by extending the handler signature to utilize them:

void Connect(const error_code& errorCode, boost::shared_ptr<asio::deadline_timer> timer)
{
}

timer.async_wait(
boost::bind(&Connect, boost::asio::placeholders::error, timer));

Is using boost::bind to pass more arguments than expected safe?

Yes, this is safe and portable – as mentioned explicitly in the documentation, the bind-expression returned by boost::bind silently ignores extra arguments.

I.e., in your first example, func does not receive the values 1, 2, and 3boundFunc receives the values 1, 2, and 3 and forwards them to the contained bind-expression, which safely receives and ignores them, and then invokes func(). Likewise, in your second example, zeroArg receives the value 42 and forwards it to the contained bind-expression, which receives and ignores the value and then calls func0().

Chaining apply to bind. Why do I need to pad my array with 1 extra value?

The first argument that bind accepts is the this value to be used inside the function. So, if you use

fn.bind.apply(fn, arr)

the first item of the arr becomes the first argument of bind, eg, it's equivalent to:

fn.bind(arr[0], arr[1], arr[2] /* ... */ )

So the first item of the array becomes the this value, the second item of the array becomes the first argument, the third item of the array becomes the second argument, etc.

Here's a live example of how "padding" the array with a value at the start becomes the new this value when the function is called:

const obj = { prop: 'val' };
function foo(string, num) { console.log("calling foo ---", string, num); console.log('this:', this);}
const fn = foo.bind.apply(foo, [obj, 'str', 55]);fn();

How to pass only second argument of a bind function?

Easier to use a lambda for your problem I think:

function<void(int, double)> onSetMin = [this](int dummy, double d) { SetMin(d); };
mEventHandler.on(kParamID, onSetMin);


Related Topics



Leave a reply



Submit