Using std::move to pass in a temp lambda, or to "pull" out a temp parameter and what is the difference?
In this line,
w.set_callback( std::move([&](std::string s){ return p.print(s); }) );
you cast an rvalue to an rvalue. This is a no-op and hence pointless. Passing a temporary to a function that accepts its parameter by value is fine by default. The function argument is likely to be instantiated in place anyhow. In the worst case, it is move-constructed, which doesn't require an explicit call to std::move
on the function argument - again, as it is already an rvalue in your example. To clarify the situation, consider this different scenario::
std::function<bool(std::string)> lValueFct = [&](std::string s){ /* ... */ }
// Now it makes sense to cast the function to an rvalue (don't use it afterwards!)
w.set_callback(std::move(lValueFct));
Now for the other case. In this snippet
void set_callback(callback_fn callback)
{
m_callback = std::move(callback);
}
you move-assign to m_callback
. This is fine, as the parameter is passed by value and not used afterwards. One good resource on this technique is Item 41 in Eff. Modern C++. Here, Meyers also points out, however, that while it's generally fine to employ pass-by-value-then-move-construct for initialization, it's not necessarily the best option for assignment, because the by-value parameter must allocate internal memory to hold the new state, while that could use an existing buffer when directly copied from a const
-qualified reference function parameter. This is exemplified for std::string
arguments, and I'm not sure how this can be transferred to std::function
instances, but as they erase the underlying type, I could imagine this being an issue, especially for larger closures.
I guess I don't understand why this code works with two std::moves... which implies that I still don't understand what std::move is doing
std::move
is there to make it explicit in cases you are intending to move from an object. Move semantics are designed to work with rvalues. Thus, std::move()
takes any expression (such as an lvalue) and makes an rvalue out of it. This usage arises typically when you need to allow an lvalue to be passed to the function overloads that accept rvalue reference as an argument, such as move constructors and move assignment operators. The idea of moving is to efficiently be transferring resources instead of making copies.
In your snippet you're not using std::move()
in an invalid way, hence this code works. In the rest of the answer we try to see whether this usage is advantageous or not.
Should I use std::move to pass in the lambda
Seemingly no, you have no reason of doing that in the snippet. First of all, you are calling move()
on a what is already an rvalue. Further, syntactically, set_callback()
is receiving its std::function<bool(std::string)>
argument by value, of which your lambda is initializing an instance just fine at present.
Should I use std::move in the set_callback() function
It isn't 100% clear what you are gaining by using the move version of the assignment operator onto the m_callback
member variable, instead of the regular assignment. It won't cause any undefined behavior though, as you are not trying to use the argument after moving it. Also, since C++11 the callback
parameter in set_callback()
will be move constructed for rvalues such as your temporary, and copy constructed for an lvalue, such as if you'd call it like this:
auto func = [&](std::string s){ return p.print(s); };
w.set_callback(func);
What you need to be considering is whether inside the method, moving is better then copying in your case. Moving involves its own implementation of the move assignment for the relevant type. I'm not just saying QOI here, but consider that when moving you need to release whatever resource m_callback
was holding up to that point, and for the scenario of moving from a constructed instance (as we've covered that callback
has been either copy constructed or move-constructed from its argument), that's adding to the cost this construction already had. Not sure that such a moving overhead applies in your case, but still your lambda is a not obviously expensive to copy as it is. That said, opting for two overloads, one taking a const callback_fn& callback
and copy-assigning inside and one taking a callback_fn&& callback
and move-assigning inside would allow to mitigate this potential issue altogether. As in either one you don't construct anything for the parameter and overall you're not necessarily releasing old resources as an overhead, as when performing the copy-assignment, one can potentially use the already existing resources of the LHS by copying onto them instead of releasing it prior to moving the ones from the RHS.
I know that I can pass by value, but my goal was to move the temp so that I don't have a copy of it (perfect forwarding?)
In the context of type deduction (template
or auto
), a T&&
is a forwarding reference, not an rvalue reference. As such you only have to write the function once (template function, no overloads), and relying internally on std::forward
(equivalent to static_cast<T&&>
) will make sure that in any use-case, the above described path for using the two overloads is preserved in terms of the cost being a copy-assignment for an lvalue call and a move-assignment for an rvalue call:
template<class T>
void set_callback(T&& callback)
{
m_callback = std::forward<T>(callback);
}