Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Semantics of std::bind and/or std::forward

I find it very confusing that the following code fails to compile

#include <functional>

class Mountain {
public:
  Mountain() {}
  Mountain(const Mountain&) = delete;
  Mountain(Mountain&&) = delete;
  ~Mountain() {}
};

int main () {
  Mountain everest;
  // shouldn't the follwing rvalues be semantically equivalent?
  int i = ([](const Mountain& c) { return 1; })(everest);
  int j = (std::bind([](const Mountain& c) {return 1;},everest))();
  return 0;
}

The compilation error being:

$ g++ -std=c++20 test.cpp -o test
In file included from test.cpp:1:
/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/functional:486:26: error: no
      matching constructor for initialization of 'tuple<Mountain>'
        : _M_f(std::move(__f)), _M_bound_args(std::forward<_Args>(__args)...)
                                ^             ~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/10/../../../../include/c++/10/functional:788:14: note: in
      instantiation of function template specialization 'std::_Bind<(lambda at test.cpp:14:22)
      (Mountain)>::_Bind<Mountain &>' requested here
      return typename __helper_type::type(std::forward<_Func>(__f),
             ^
test.cpp:14:17: note: in instantiation of function template specialization 'std::bind<(lambda at
      test.cpp:14:22), Mountain &>' requested here
  int j = (std::bind([](const Mountain& c) {return 1;}, everest))();
                ^
...

So std::bind sneakily tries to copy everest even though the lambda only wants a reference to it. Am I rubbing against a weird edge case that nobody cares about (eg. it is always possible to just lambda-capture a reference to everest) or is there a rationale? If the rationale is that bind is protecting me from calling the lambda after everest was destroyed, is there an unsafe version of bind that would not do that?

like image 469
Christos Perivolaropoulos Avatar asked Jan 24 '23 08:01

Christos Perivolaropoulos


1 Answers

Yes, arguments to std::bind would be copied (or moved).

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

You can use std::cref (or std::ref) instead. E.g.

int j = (std::bind([](const Mountain& c) {return 1;}, std::cref(everest)))();
//                                                    ^^^^^^^^^^       ^

LIVE

like image 127
songyuanyao Avatar answered Feb 08 '23 10:02

songyuanyao