I have been trying to understand how std::bind works. So working up with different examples. Below is the sample program whose output i am unable to understand.
VERSION 1
class NAME
{
public:
void f()
{
std::cout<<"f"<<std::endl;
}
NAME()
{
std::cout<<"default constructor"<<std::endl;
}
NAME(const NAME&)
{
std::cout<<"copy constructor"<<std::endl;
}
};
int main()
{
std::cout << "Hello World" << std::endl;
NAME n;
std::function<void ()> callable = std::bind(&NAME::f, n);
return 0;
}
The output of the above version 1 is as follows:
Hello World
default constructor
copy constructor
copy constructor
I know that the argument passed will be copied, so the copy constructor should be called only one time but in the output above the copy constructor is called twice. Why/how is this happening? Is it because the new callable that is created using std::bind will be used to initialize another callable using std::function on the lhs?
VERSION 2
int main()
{
std::cout << "Hello World" << std::endl;
NAME n;
std::function<void ()> callable = std::move(std::bind(&NAME::f, n));
return 0;
}
The output of VERSION 2 is as follows:
Hello World
default constructor
copy constructor
copy constructor
copy constructor
In the output above(for version 2) when i use std::move, why/how is the copy constructor called thrice?
VERSION 3
int main()
{
std::cout << "Hello World" << std::endl;
NAME n;
auto callable = std::bind(&NAME::f, n);
return 0;
}
The output of version 3 is as follows:
Hello World
default constructor
copy constructor
In this case(version 3) why/how is the copy constructor called only once?
VERSION 4
int main()
{
std::cout << "Hello World" << std::endl;
NAME n;
auto callable = std::move(std::bind(&NAME::f, n));
return 0;
}
The output of version 4 is as follows:
Hello World
default constructor
copy constructor
copy constructor
What happens in this case(version 4) when we use auto
and std::move()
, why/how is the copy constructor called twice.
PS: The program is executed on an online compiler: Online compiler Used
Edit: Upon reading the comments i have a further question/doubt:
Question 1
If i use auto callable_1 = std::bind(&NAME::f, n);
and std::function<void ()> callable_2 = std::bind(&NAME::f, n);
then are the types of callable_1
and callable_2
different? If yes, then what is the type deduced by auto
for callable_1
? Or is it that the type deduced by auto
for callable_1
will be an unnamed class object just like for a lambda
.
Question 2
I have one further question: As i did in my case, if i pass n
instead of &n
in std::bind
then is the code legal? For example, if i write std::function<void ()> callable_1 = std::bind(&NAME::f, n);
and auto callable_2 = std::bind(&NAME::f, n);
then are both of these illegal? What if i passed ref(n)
instead of n
in both of these cases, then will they be illegal or legal?
Question 3
Are the statements auto callable = [&n]{n.f()};
and auto callable = std::bind(&NAME::f, cref(n));
equivalent(in functionality) and maybe in some other ways and is there any reason(advantage) to pick one over another apart from coding style?
Question 4
What if i write auto callable = std::bind(&NAME::f, 1_); callable(n); callable(std::ref(n));
. Is the statement callable(n)
illegal? And what about the statement callable(std::ref(n));
.
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.
// default constructor will be called
NAME n;
// bind makes a copy of n into an internal struct, copy constructor of n is called.
auto fn = std::bind(&NAME::f, n);
// assignment operator makes a copy of members of fn to members of std::function.
// This is also a copy constructor call of NAME
std::function<void ()> callable = fn;
Note: Instead of bind, you can also use lambda's
std::function<void ()> callable([&n]{n.f()});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With