4

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?

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • What is `nc`? Could you [edit] your question to include a definition of both `everest` and `nc`? – YSC May 19 '21 at 10:43
  • 2
    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`. – super May 19 '21 at 10:43
  • 1
    Please provide [mcve]. Your example is missing symbols `everest` and `nc`! Use this as starting point: https://godbolt.org/z/zxh5sYafe ! – Marek R May 19 '21 at 10:43
  • `bind` is for *"capture"*, lambda equivalent would be `[nc]() { return 1; }` (which won't compile for similar reason). – Jarod42 May 19 '21 at 10:46
  • Oops, copied an old version of the code. Fixed it now. – Christos Perivolaropoulos May 19 '21 at 10:49

1 Answers1

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

songyuanyao
  • 169,198
  • 16
  • 310
  • 405