5

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));.

francesco
  • 7,189
  • 7
  • 22
  • 49
Jason
  • 36,170
  • 5
  • 26
  • 60
  • All the questions here seem to revolve around two topics. 1. - the overhead of `std::function`. 2. - the pessimizations possible from `std::move`. – Drew Dormann Aug 24 '21 at 16:55
  • On a side note: `NAME::f()` expects a `NAME*` pointer to an object for its `this` parameter, not a `NAME` object itself. So the call to `std::bind()` should be using `&n` instead. Then no copies of `n` would be made at all: `std::bind(&NAME::f, &n)` – Remy Lebeau Aug 24 '21 at 17:00
  • @RemyLebeau Yes i already know that if we use `&n` instead of `n` in `std::bind` then no copy will be made at all. In this case my question is that if we pass `n` instead of `&n` just like i did, then is the code legal ? – Jason Aug 24 '21 at 17:04
  • @JasonLiam I want to say no, `f()` would not be passed a proper `this` value if `n` is copied. But that doesn't seem to be the case (unless this is compiler-dependent behavior): https://ideone.com/MxN29P. On the other hand, https://en.cppreference.com/w/cpp/utility/functional/bind says: "*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.*" – Remy Lebeau Aug 24 '21 at 17:15

3 Answers3

4

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.

francesco
  • 7,189
  • 7
  • 22
  • 49
  • 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 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? – Jason Aug 24 '21 at 17:34
  • I have edited my question(in the Edit part), can you also answer those remaining questions. – Jason Aug 24 '21 at 18:49
2
// 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()});
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • A valid observation, but I don't think this is an _answer_ to the questions asked. This perhaps should be a comment. – Drew Dormann Aug 24 '21 at 16:51
  • Does this mean if i use `auto callable_1 = std::bind(&NAME::f, n);` and `std::function callable_2 = std::bind(&NAME::f, n);` then the types of `callable_1` and `callable_2` are 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? – Jason Aug 24 '21 at 16:59
  • callable_1 will have the same signature as callable2, std::function it will be different instances. the lambda will be unnamed (it will have some strange generated type), but you can assign it to a std::function if the signature of the lambda matches that of the std::function. You can let the compiler deduce a std::function type from a lambda but its somewhat advanced (I'll add another answer for that) – Pepijn Kramer Aug 24 '21 at 17:12
  • @JasonLiam `std::bind()` does not return a `std::function`, but rather an unspecified implementation-defined type that is assignable to a `std::function`. So yes, `callable_1` and `callable_2` will have different types – Remy Lebeau Aug 24 '21 at 17:17
1
#pragma once

namespace details
{
    template<typename T>
    struct memfun_type
    {
        using type = void;
    };

    template<typename RetvalType, typename ClassType, typename... Args>
    struct memfun_type<RetvalType(ClassType::*)(Args...) const>
    {
        using type = typename std::function<RetvalType(Args...)>;
    };

} // details

template<typename Fn>
typename details::memfun_type<decltype(&Fn::operator())>::type 
make_std_function(Fn const& fn)
{
    return fn;
}

And now you can make std::function<RetvalType(Args...)> from lambdas like this

auto std_fn = make_std_function([](int n){return 2*n;});
int answer = std_fn(2);
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
  • I have added some more questions in the "Edit" part of my question. Can you answer them too. You can just append them to your previous answer. – Jason Aug 25 '21 at 05:23
  • I don't have enough experience to make statements on std::bind created functions. I switched to lambdas long ago. I do have one warning on using ref's though. You must be sure the referenced objects are still alive when the constructed function (doesn't matter if its created by bind or lambda). So passing local (stack) variables can be tricky (and things get even more interesting when you pass lambdas or std::functions with refs to other threads) – Pepijn Kramer Aug 25 '21 at 06:44