Semantics of std::bind and/or std::forward
Asked Answered
M

1

5

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?

Maggard answered 19/5, 2021 at 10:38 Comment(5)
What is nc? Could you edit your question to include a definition of both everest and nc?Notary
Declarations for nc and everest are missing in your example code. std::bind was made before lambdas existed. You should generally always use lambdas, never bind. If you want to use references with std::bind you need to use std::ref or std::cref.Reefer
Please provide minimal reproducible example. Your example is missing symbols everest and nc! Use this as starting point: godbolt.org/z/zxh5sYafe !Seleucia
bind is for "capture", lambda equivalent would be [nc]() { return 1; } (which won't compile for similar reason).Untidy
Oops, copied an old version of the code. Fixed it now.Maggard
D
8

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

Demona answered 19/5, 2021 at 10:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.