简体   繁体   中英

Should I pass a lambda by const reference.

Typically I use the following pattern when accepting a lambda as an argument to a function (A template class passed-by-value):

template <class Function>
void higherOrderFunction(Function f) {
     f();
}

Does this copy (the closure of) the argument? If so, is there anything wrong with accepting the lambda by const reference instead?

template <class Function>
void higherOrderFunction(const Function& f) {
  f();
}

A simple test seems to indicate that this works fine, but I want to know if there are any special considerations that I should be aware of.

If you pass by value you will copy the closure object (assuming you don't define the lambda inline, in which case it will be moved). This might be undesirable if the state is expensive to copy, and will fail to compile if the state is not copyable.

template <class Function>
void higherOrderFunction(Function f);

std::unique_ptr<int> p;
auto l = [p = std::move(p)] {}; // C++14 lambda with init capture
higherOrderFunction(l);         // doesn't compile because l is non-copyable 
                                // due to unique_ptr member
higherOrderFunction([p = std::move(p)] {}); // this still works, the closure object is moved

If you pass by const reference, then you cannot pass a mutable lambda that modifies its data members as the argument to higherOrderFunction() because a mutable lambda has a non- const operator() , and you cannot invoke that on a const object.

template <class Function>
void higherOrderFunction(Function const& f);

int i = 0;
higherOrderFunction([=]() mutable { i = 0; }); // will not compile

The best option is to use a forwarding reference. Then higherOrderFunction can accept either lvalues or rvalues that the caller passes.

template <class Function>
void higherOrderFunction(Function&& f) {
     std::forward<Function>(f)();
}

This allows the simple cases as well as the ones mentioned above to compile. For a discussion of why std::forward should be used, see this answer .

Live demo

A copy is a copy, so you cannot mutate the original, might have some performance impact when a lot of data is involved:

#include <iostream>
using namespace std;

template<typename Fn>
void call_value(Fn f) { f(); }
template<typename Fn>
void call_ref(Fn & f) { f(); }
template<typename Fn>
void call_cref(Fn const & f) { f(); }

struct Data {
  Data() {}
  Data(Data const &) {
    cout << "copy" << endl;
  }
  Data(Data &&) {
    cout << "move" << endl;
  }
};

int main(int, char **) {
  Data data;

  auto capref = [&data] () {};
  cout << "capture by value, so we get a ";
  auto capcp = [data] () {};

  cout << " the lambda with a reference ... ";
  call_value(capref);
  cout << " could now be called and mutate .. ";
  call_ref(capref);
  call_cref(capref);
  cout << " but won't, as it had to be declared mutable " << endl;

  cout << "the lambda with an instance: ";
  call_value(capcp);
  cout << "but not ";
  call_ref(capcp);
  call_cref(capcp);
  cout << " the reference versions " << endl;

  bool en = false;
  auto trigger = [en](bool enable = true) mutable {
    if (en) {
      cout << "fire!" << endl;
    }
    if (en or enable) {
      en = true;
    }
  };
  cout << "won't shoot" << endl;
  trigger(false);
  call_value(trigger);
  trigger(false);
  call_ref(trigger);
  cout << "and now ... ";
  trigger(false);
  // const ref won't work
  return 0;
}

See it in action .

Don't forget: lambdas are mere syntactic sugar for callable classes. (But extremely helpful)

The STL does not use references for function objects in parameters of templates. So, most probably, neither should you, unless you have very specific needs and understand in depth why a ref will help you in your case, and what are the risks when you do so.

If you do use references, I'm not sure the end result would be easy to read and extremely maintainable though, so maybe even then you should rather find alternative solutions, like eg maintaining your state in a reference-captured var, and continue to pass your lambda object by value. Otherwise, please at least add some comments indicating why you did something complicated and non-idiomatic, instead of the simple thing the standard uses all over the place in its own library.

Anyway there are places where perfectly forwarded universal references would be perfectly fine and with small risk of extra complexity: if the function object is simply forwarded once to a param of another function. And well, that's why you should stick to the simplest possible stuff unless you understand in great depth (and you expect that all the future maintainers of your code also will); because blindly using std::forward on an universal ref is not safe in the general case: for example it would be extremely dangerous to use that in the body of a loop. And even then, you probably only want to do that when you expect the future maintainers will also all known that and not extend the code in a seemingly simple but incorrect way.

So as a general rule; only use particular tricky C++ feature if you know extremely well why you use them, all the details of how they work, and all the potential risks associated. Properly using C++ references in C++ is tricky because if you do any mistake (among hundreds of potential ones), the compiler will happily generate a binary for your broken program, most of the time with not even a warning telling you you have done some shit. Passing by value is way less dangerous, so at least in contexts where the standard does so, so should you. Unless you are an expert, and only work with experts.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM