Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using std::move to pass in a temp lambda, or to "pull" out a temp parameter and what is the difference?

Tags:

c++

c++11

stdmove

I have the following (contrived) code where I have a printer class with a single print function in it and a working class that processes a string and then calls a callback function to the print function:

#include <functional>
#include <iostream>

using callback_fn = std::function<bool(std::string)>;

class printer
{
public:   
    bool print(std::string data)
    {
        std::cout << data << std::endl;
        return true;
    }
};

class worker
{
public:   
    callback_fn m_callback;
    void set_callback(callback_fn callback)
    {
        m_callback = std::move(callback);  // <-- 1. callback is a temp, so what does std::move do here?
    }
    void process_data(std::string data)
    {
        if (!m_callback(data)) { /* do error handling */ }
    }
};

int main() {
    printer p;
    worker w;

    w.set_callback( std::move([&](std::string s){ return p.print(s); }) ); // <-- 2. what does std::move do here?
    w.process_data("hello world2");
}

Note: I have std:: move() called twice... now this works (surprisingly to me) but I have both only to show what I am trying. My questions are:

  1. Should I use std::move() in the set_callback() function to "pull" out the temp, and if I use this is there really a copy or does std:: move() mean that its not really a copy?
  2. Should I use std:: move() to pass in the lambda... and is that even correct.
  3. 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 - so if someone can elighten me on what is going on here that would be great!
  4. I know that I can pass by value, but my goal is to move the temp so that I don't have a copy of it. Is that what is meant by perfect forwarding?

My example can be seen here in wandbox: https://wandbox.org/permlink/rJDudtg602Ybhnzi

UPDATE The reason for me trying to use std::move was to avoid copying the lambda around. (I think that is called forwarding/perfect-forwarding)...but I think I am making a hash of it!

like image 675
code_fodder Avatar asked Sep 13 '18 07:09

code_fodder


People also ask

What does std :: move do?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Does C++ automatically move?

That's pretty much the only time you should write std::move , because C++ already uses move automatically when copying from an object it knows will never be used again, such as a temporary object or a local variable being returned or thrown from a function. That's it.


3 Answers

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.

like image 156
lubgr Avatar answered Sep 16 '22 20:09

lubgr


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);
}
like image 21
Geezer Avatar answered Sep 19 '22 20:09

Geezer


According to c++ reference std::move is just a cast to rvalue reference.

  1. You don't need to use std::move inside your set_callback method. std::function is CopyConstructible and CopyAssignable (docs), so you can just write m_callback = callback;. If you use std::function move constructor, the object you moved from will be "unspecified" (but still valid), in particular it might be empty (which doesn't matter because it is a temporary). You can also read this topic.
  2. Same thing here. Lambda expression is a temporary and expires after the call to set_callback, so it doesn't matter if you move it or copy it.
  3. The code works, because your callbacks are constructed (move constructed) correctly. There is no reason for the code not to work.

Below is the example with the situation when std::move makes a difference:

callback_fn f1 = [](std::string s) {std::cout << s << std::endl; return true; };
callback_fn f2 = f1;
f1("xxx");  // OK, invokes previously assigned lambda
f2("xxx");  // OK, invokes the same as f1
f2 = std::move(f1);
f1("xxx");  // exception, f1 is unspecified after move

Output:

xxx
xxx
C++ exception with description "bad_function_call" thrown in the test body.
like image 22
pptaszni Avatar answered Sep 17 '22 20:09

pptaszni