Std::Function and Std::Bind: What Are They, and When Should They Be Used

std::function and std::bind: what are they, and when should they be used?

std::bind is for partial function application.

That is, suppose you have a function object f which takes 3 arguments:

f(a,b,c);

You want a new function object which only takes two arguments, defined as:

g(a,b) := f(a, 4, b);

g is a "partial application" of the function f: the middle argument has already been specified, and there are two left to go.

You can use std::bind to get g:

auto g = bind(f, _1, 4, _2);

This is more concise than actually writing a functor class to do it.

There are further examples in the article you link to. You generally use it when you need to pass a functor to some algorithm. You have a function or functor that almost does the job you want, but is more configurable (i.e. has more parameters) than the algorithm uses. So you bind arguments to some of the parameters, and leave the rest for the algorithm to fill in:

// raise every value in vec to the power of 7
std::transform(vec.begin(), vec.end(), some_output, std::bind(std::pow, _1, 7));

Here, pow takes two parameters and can raise to any power, but all we care about is raising to the power of 7.

As an occasional use that isn't partial function application, bind can also re-order the arguments to a function:

auto memcpy_with_the_parameters_in_the_right_flipping_order = bind(memcpy, _2, _1, _3);

I don't recommend using it just because you don't like the API, but it has potential practical uses for example because:

not2(bind(less<T>, _2, _1));

is a less-than-or-equal function (assuming a total order, blah blah). This example normally isn't necessary since there already is a std::less_equal (it uses the <= operator rather than <, so if they aren't consistent then you might need this, and you might also need to visit the author of the class with a cluestick). It's the sort of transformation that comes up if you're using a functional style of programming, though.

std::function works beautifully with std::bind - but why?

Q: What are the basic rules that effectively determine: dist(rng) being used - I don't see why std::bind would enforce this interaction. A lot of interactions seem based around operator () methods.

std::bind performs function composition. The first argument must be a function object, i.e. something callable like a function (e.g. a normal function, or a class with an overloaded operator()).

A call to std::bind makes copies of its arguments, "binds" the copies of the arguments to the first argument (the function object), and returns a new function object that will invoke the copy of the function object.

So in a simple case:

int f(int i) { return i; }
auto f1 = std::bind(f, 1);

this binds the value 1 to the function f, creating a new function object that can be called with no arguments. When you invoke f1() it will invoke f with the argument 1, i.e. it will call f(1), and return whatever that returns (which in this case is just 1).

The actual type of the thing returned by std::bind(f, 1) is some implementation-specific class type, maybe called something like std::__detail::__bind_type<void(*)(int), int>. You're not meant to refer to that type directly, you would either capture the object using auto or store it in something else that doesn't care about the precise type, so either:

auto f1 = std::bind(f, 1);

or:

std::function<int()> f1 = std::bind(f, 1);

In your more complex case, when you call std::bind(decltype(p_dist){0, p - 1}, std::ref(rng))) you get a new function object that contains a copy of the temporary decltype(p_dist){0, p - 1} and a copy of the reference_wrapper<std::mt19937> created by std::ref(rng). When you invoke that new function object it will call the contained distribution, passing it a reference to rng.

That means it is callable with no arguments, and it will call the contained random number distribution with the contained random engine, and return the result.

Q: std::function is helpfully referred to as 'a general-purpose polymorphic function wrapper' on cppreference.com. So is it a function that encapsulates a uint64_t return type?

A std::function<uint64_t()> is a wrapper for a function object that is callable with no arguments and that returns a uint64_t (or something implicitly convertible to uint64_t). It can be used to store a copy of an arbitrary function object that is copy constructible, and callable with no arguments, and returns something convertible to uint64_t.

(And more generally, a std::function<R(A1, A2 ... AN)> is a wrapper for a function object that returns R when called with N arguments, of types A1, A2 ... AN.)

Since the result of your std::bind call is a copy constructible function object that is callable with no arguments and returns a uint64_t, you can store the result of that std::bind call in a std::function<uint64_t()>, and when you invoke the function it will invoke the result of the bind call, which will invoke the contained distribution, and return the result.

Or again, making use of operator () syntax to drive the notion of a function?

I'm not sure what this means, but in general it's true that in C++ we often talk about "function objects" or "callables" which are generalisations of functions, i.e. something that can be invoked using function call syntax, e.g. a(b, c)

How to use std::bind with std::function in order to pass method as a callback?

You miss placeholders:

auto callback = std::bind(&TV_DepthCamAgent::progress_callback,
this,
std::placeholders::_1,
std::placeholders::_2);

but simpler, IMO, is to use lambda:

auto callback = [this](int count, int copied_file){
return this->progress_callback(count, copied_file);
};

How does std::bind Results in calling the Copy Constructor Several Times

First of all, according to the rules for move constructors, no implicit move constructor is defined for the class NAME. Further, from the Notes here:

If only the copy constructor is provided, all argument categories
select it (as long as it takes a reference to const, since rvalues can
bind to const references), which makes copying the fallback for
moving, when moving is unavailable.

So, whenever you use std::move you are eventually calling a copy constructor. This explains why Version 4 (respectively, Version 2) has an additional call to the copy constructor compared to Version 3 (respectively, Version 1).

Let us see the remaining copy constructors.

As you correctly pointed out, a copy constructor is called from passing second argument of std::bind. This account for the first call in all versions.

When you declare

 std::function<void ()> callable = std::bind(&NAME::f, n);

you are calling the constructor of std::function, passing a single parameter std::bind(&NAME::f, n) which is then, again, copied. This account for the second call of copy constructor of version 1, and the third call in version 2. Notice that mandatory copy elision does not apply here, because you are not passing a std::function object.

Finally, when you use

auto callable = std::bind(...)

you are declaring a variable which is of an unnamed type, and contains the result of the call to std::bind.
No copy is involved in the declaration. This is why version 3 has one less call to copy constructor compared to version 1.

Answers to the additional questions

1.

The types of callable_1 and callable_2 are different. callable_2 is a std::function object, while callable_1 is an unspecified type, the result of std::bind. Also, it is not a lamda. To see this, you can run a code like

   auto callable_1 = std::bind(&NAME::f, n);
std::function<void ()> callable_2 = std::bind(&NAME::f, n);
// a generic lambda
auto callable_3 = [&]() { n.f(); };
std::cout << std::boolalpha;
std::cout << std::is_bind_expression<decltype(callable_1)>::value << std::endl;
std::cout << std::is_bind_expression<decltype(callable_2)>::value << std::endl;
std::cout << std::is_bind_expression<decltype(callable_3)>::value << std::endl;

See it live on Coliru.

2.

As noted by @RemyLebeau, a strict interpretation of the Notes in the reference of std::bind

As described in Callable, when invoking a pointer to non-static member
function or pointer to non-static data member, the first argument has
to be a reference or pointer (including, possibly, smart pointer such
as std::shared_ptr and std::unique_ptr) to an object whose member will
be accessed.

would suggest that the code must be called with &n and a call with n would be illegal.

However, a call to operator() results in a std::invoke. From the reference of std::invoke we read (little reformatting by me):

If f is a pointer to member function of class T:

a) If std::is_base_of<T, std::decay_t<decltype(t1)>>::value is true, then INVOKE(f, t1, t2, ..., tN) is equivalent to (t1.*f)(t2,
..., tN)

b) If std::decay_t<decltype(t1)> is a specialization of
std::reference_wrapper, then INVOKE(f, t1, t2, ..., tN) is equivalent
to (t1.get().*f)(t2, ..., tN)

c) If t1 does not satisfy the previous
items, then INVOKE(f, t1, t2, ..., tN) is equivalent to ((*t1).*f)(t2,
..., tN).

According to this, calling std::bind with n (case a)) or &n (case c)) should be equivalent (apart from the additional copy if you use n), because std::decay_t<decltype(n)> gives NAME and std::is_base_of<NAME, NAME>::value is true (see reference for std::is_base_of).
Passing ref(n) corresponds to the case b), so again it should be correct and equivalent to the other cases (apart from the copies discussed above).

3.

Notice that cref gives you a reference wrapper to const NAME&. So you will not be able to call callable because NAME::f is not a const member function. In fact, if you add a callable(); the code does not compile.

Apart from this issue, if you use instead std::ref or if NAME::f is const, I do not see a fundamental difference between auto callable = [&n]{n.f()}; and auto callable = std::bind(&NAME::f, ref(n));. With these regards, notice that:

The arguments to bind are copied or moved, and are never passed by
reference unless wrapped in std::ref or std::cref.

Personally, I find the lambda syntax much clearer.

4.

From the reference for std::bind, under operator() we read

If the stored argument arg is of type T, for which
std::is_placeholder::value != 0 (meaning, a placeholder such as
std::placeholders::_1, _2, _3, ... was used as the argument to the
initial call to bind), then the argument indicated by the placeholder
(u1 for _1, u2 for _2, etc) is passed to the invokable object: the
argument vn in the std::invoke call above is std::forward(uj) and
the corresponding type Vn in the same call is Uj&&.

Thus, the effect of using placeholders falls back to the cases of question 1. The code indeed compiles with different compilers.



Related Topics



Leave a reply



Submit