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 there is any possibility you are storing a copy of the std::function , pass by value. Otherwise, either way is roughly equivalent: the only downside to by-value is if you are taking the same bulky std::function and having one sub method after another use it. Barring that, a move will be as efficient as a const& .
Typically, a lambda's function call operator is const-by-value, but use of the mutable keyword cancels this out. It doesn't produce mutable data members. The mutable specification enables the body of a lambda expression to modify variables that are captured by value.
Pass Using Const Reference in C++ Now, we can use the const reference when we do not want any memory waste and do not change the variable's value. The above code will throw a compile error as num = num +10 is passed as a const reference.
When you pass by const reference, you take the argument in by reference (avoiding making any copies of it), but cannot make any changes to the original object (much as would happen when you would take the parameters in by value).
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With