1

Consider this code:

class A {
private:
    std::string data;
public:
    void set_data(std::string&& data) {
        this->data = std::move(data); // line 6
    }
};

int main() {
    std::string move_me = "Some string";
    A a;
    a.set_data(std::move(move_me)); // line 13
}

I'm understand that we need to call std::move() on line 13 so that it casts an lvalue to rvalue reference (does that sound correct? I'm new to this).

However, on line 6, do we need to use std::move() again? I assume not, since we already passed an rvalue reference and std::string's move constructor will be called. Is that correct?

bobl
  • 31
  • 3
  • yes, yes, no (padding-chars) – apple apple Sep 16 '18 at 11:33
  • I recommand to not use `this->data` and just use something like `m_data`. It is needed because data is considered as lvalue reference. – Antoine Morrier Sep 16 '18 at 11:34
  • 4
    An l-value is essentially a named variable, therefore `data` on line 6 is an l-value and must be recast. – George Sep 16 '18 at 11:37
  • The signature should be changed to `void set_data(std::string data)`. This way if the caller can't (or forgets to) `move`, the code will still compile. – HolyBlackCat Sep 16 '18 at 11:41
  • @HolyBlackCat Although that approach may be sub-optimal for lvalues, in which case const lvalue and rvalue overloads might be a better approach. – juanchopanza Sep 16 '18 at 11:47
  • On line 6 i would forward instead of move, something like `this->data = std::forward(data);` –  Sep 16 '18 at 11:50
  • @OZ17 I don't see any good reason to do that. – juanchopanza Sep 16 '18 at 11:53
  • 2
    @OZ17 It doesn't make much sense to use `forward` without a forwaring reference. – HolyBlackCat Sep 16 '18 at 11:53
  • Guys if you only have `this->data = data;` copy constructor is called on that line. You still need to use move on this line or use forward which is more intuitive in my opinion. Please correct me if i am wrong. –  Sep 16 '18 at 12:00
  • 1
    @OZ17 `std::forward` and `std::move` are not the same. You should only `std::forward` something that was passed as a `template` cv-unqualified *rvalue reference*. There is no `template` involved in `set_data` method – Fureeish Sep 16 '18 at 12:02
  • @Fureeish, so, to paraphrase. We'd need to use `std::forward` instead of `std::move` only if instead of `std::string&&` I had something generic, right? That is, only if `set_data` was something like this: `template set_data(T&& data)`? – bobl Sep 16 '18 at 12:13
  • 3
    @bobl yes, that's correct! [This talk](https://youtu.be/wQxj20X-tIU?t=16m44s) by Scott Meyers (the link should come with timestamp, but it it's broken, it's 16m44s) explains it pretty well. The thing is that `T&&` *does not necessarily always mean rvalue reference* (that's a lie, but it's a comfortable lie). Sometimes, due to *reference collapsing*, it becomes an lvalue reference and `std::forward` 'detects it` and forwards the correct type (either lvalue or rvalue reference) further – Fureeish Sep 16 '18 at 12:17
  • @Fureeish, great. Thanks for the link. I think I'll wath the whole talk anyway! :) – bobl Sep 16 '18 at 12:25
  • There is a SO thread https://stackoverflow.com/questions/46053971/stdforward-in-non-template-function about using `std::forward` or `std::move` in this situtation. Me and my colleagues are using `std::forward` ever since and I'll still stick to it. I don't see any reason for not using it in non template functions like e.g. here. –  Sep 16 '18 at 12:40
  • 1
    I'm so sorry for you then. It means that you and your colleagues don't know that `std::forward` does and prefer to write code that is expressing something else than intended. I suggest you learn more about differences between `std::move` and `std::forward` from the talk I linked 3 comments above – Fureeish Sep 16 '18 at 13:20
  • Possible duplicate of [Why do I have to call move on an rvalue reference?](https://stackoverflow.com/questions/39386441/why-do-i-have-to-call-move-on-an-rvalue-reference) – xskxzr Sep 17 '18 at 08:49

3 Answers3

5

However, on line 6, do we need to use std::move() again?

Yes. Why? Because inside set_data, data(the argument) is an lvalue, because it has a name. Both std::moves are necessary to actually move move_me to data in a.

Without the std::move on line 6, move_me would not be moved, because that would call std::string(const std::string&), not std::string(std::string&&).

Remember - if something has a name, it is an lvalue.

Fureeish
  • 12,533
  • 4
  • 32
  • 62
1

It seems both answers are correct , I am just adding paragraph from the standard that explains why it's correct to use std::move() in line #6 and line #13 and why it's is an lvalue even though the type is an rvalue in line #6.

The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise. 5.1.1[expr.prim.general]/8

So applying this rule from the standard we can hopefully get our answers straight.

lvalue

    // move_me is identifier of a variable denotes to itself the result is lvalue
    std::string move_me = "Some string";

rvalue

   // constructing temporary e.g no  identifier is an rvalue
   std::string("Some string") ; 

lvalue

  // the variable data has type rvalue reference to move_ms, it denotes entity move_ms
  // the result is lvalue
  void set_data(std::string&& data);

lvalue

// the variable data has type  lvalue reference to move_ms, 
//it denotes entity move_ms the result is lvalue
void set_data(std::string& data);

lvalue or rvalue - Universal references

//the variable data has type universal reference it either holds lvalue or rvalue
template<typename T> void setdata(T && data) ;

So, rvalue reference is not rvalue , things can go wrong

Base(Base const & rhs); // non-move semantics
Base(Base&& rhs); // move semantics 

if you miss to use std::move()

 Derived(Derived&& rhs) : Base(rhs) // wrong: rhs is an lvalue
 {
  // Derived-specific stuff
 }

The correct version is :

  Derived(Derived&& rhs) : Base(std::move(rhs)) // good, calls Base(Base&& rhs)
  {
  // Derived-specific stuff
  }

Also

  • creating lvalue reference to lvalue - OK
  • creating rvalue reference to rvalue - OK
  • creating lvalue const reference to rvalue - OK
  • creating lvalue reference to rvalue - compile ERROR
-1

You need it in both on line #6 and line #13.

There is a nice post from Scott Mayers on the subject.

The most acceptable ways are

// 1: full flexibility for the caller to decide where the data state comes from
struct X
{
    Y data_;
    explicit X(const Y& data) : data_(data) { }
    explicit X(Y&& data) : data_(std::move(data)) { }
};

// 2: forced copy or move at the call site and zero-copy move into the internal state of the X
struct X
{
    Y data_;
    explicit X(Y data) : data_(std::move(data)) { }
};

// 3: same as the setter below, but can have quite different forms based on what exactly is required
struct X
{
    Y data_;
    template <class... Z>
    explicit X(Z&&... arg) : data_(std::forward<Z>(args)...) { }
}

The setter is best done in the "transparent" style delegating effectively to the assignment operator of the field.

template <typename Arg> void setData(Arg&& arg) {
    data_ = std::forward<Arg>(arg);
}

I would recommend to code a simple class with all sorts of copy/move constructors/operators instrumented with debug prints and play with such class a bit to develop the intuition of how to work with &&, std::forward, and std::move. That's what I did back in the days, anyway.

bobah
  • 18,364
  • 2
  • 37
  • 70
  • this does not answer the question – apple apple Sep 16 '18 at 11:37
  • But what if I wanted specifically to have a setter method, alongside with move constructors that you showed? – bobl Sep 16 '18 at 11:43
  • I think 5 people think the same as me, though. – apple apple Sep 16 '18 at 11:44
  • @appleapple - sure dude, if it makes it for you – bobah Sep 16 '18 at 11:44
  • 1
    @bobl - `template setData(T&& data) { _data = std::forward(data); }` is the correct syntax that delegates the moving decision into the assignment operator of the _data. – bobah Sep 16 '18 at 11:45
  • Let's not start a flame war here. I didn't know about `std::forward` so I definitely find @bobah's answer helpful. – bobl Sep 16 '18 at 11:47
  • @bobah your updated *recommended way* is actually over-complex, and, uhh, still not an answer. – apple apple Sep 16 '18 at 11:48
  • @bobah (I did not downvote) - Using forwarding references for the sake of overloading for lvalue/rvalue references is not a good idea. You'll get issue with overload resolution, especially in constructor. In particular, you're going to mess up with the copy/move constructor if you had any. You defer the construction of the object, so you do not have access to previously available constructors. And most of the time this is not useful at all (in constructors). Take your object by reference if you intend on storing a reference/pointer to it, take your object by value if you intend on copying it. – Holt Sep 16 '18 at 12:13
  • @bobah Thanks for the link, I definitely learned something from your answer and I hope people will upvote your answer. Although, I'll accept Fureesh's answer as it answers my question more directly, I think. – bobl Sep 16 '18 at 12:21
  • @Holt - the #2 example is probably close to your preferred way. It's mine preferred too. The main use case for forwarding references is for piece-wise construction of the members or the base class, but yes, I do tend to abuse it to save some cut/paste. Never looked at it from the resolution scope pollution perspective... – bobah Sep 16 '18 at 12:23
  • @bobl - glad you found something useful. If it does not float below 0 in a day or to I'll clean it up. – bobah Sep 16 '18 at 12:23
  • @Holt I wouldn't necessarily agree on "*take your object by value if you intend on copying it*. I assume you mean *if you intend on copying, take it by value and move*. Well, consider the situation where the copied buffer is smaller than the original. We waste memory allocation. See [this talk](https://youtu.be/xnqTKD8uD64?t=1h11m48s) by Herb Sutter (the timestamp should be 1h11m48s). He explains (and later on provides [benchmarks](https://youtu.be/xnqTKD8uD64?t=1h18m58s) (1h18m58s)) that it's sometimes not a great idea. – Fureeish Sep 16 '18 at 12:30
  • @Fureeish I was referring to construction, which Herb mentions in its talk. – Holt Sep 16 '18 at 12:32
  • Ah, I see. I'm sorry then. It seemed like an universal advice – Fureeish Sep 16 '18 at 12:37
  • @bobah, your code is correct in every way. I have used what is in your 1.) 2.) and I always use 3.) for universal references. I think the down voting came from ppl who did not see answers to `#6` and `#13` lines that @bobl was referring to. Hopefully ppl will look at it from different prospective as a set of use cases helping with production quality code rather then specific answer to specific question. Good luck! – Vladimir Venediktov Sep 16 '18 at 21:48