0

I have the following code:

//void func(const std::string &&i){
//void func(const std::string &i){
void func(const std::string i){
  std::string val{i};
}

int main() 
{ 
  std::string d = "asdf";
  func(std::move(d));
  std::cout << d << std::endl;
} 

When i is pass-by-value, d becomes empty, but d retains its form if we're passing by reference or by r-value reference. Could someone explain what is going on?

I understand that std::move doesn't actually move anything, but rather makes the variable it takes in moveable by casting it to an xvalue.

As an aside why does the code in the current state compile if d is cast to an x-value? func is currently set to take in pass by value arguments, and not by rvalue reference.

24n8
  • 1,898
  • 1
  • 12
  • 25

2 Answers2

3

When i is pass-by-value, d becomes empty,

To be accurate, d will be in some valid state not specified in the C++ standard. Empty is one possibility.

std::move itself never causes the move constructor to be called directly. Neither does binding an rvalue reference to an object cause move constructor to be called directly.

Only initialising an object with a non-const rvalue will cause the argument to be moved from. In the example, std::string i is initialised with a non-const rvalue and the move constructor will be called.

As an aside why does the code in the current state compile if d is cast to an x-value?

Because the type has a (non-deleted) move constructor. Therefore the argument can be initialised from an rvalues.

I had thought if we had std::string i, a copy of the rvalue reference is made.

std::string i is not a reference. It is a variable of type std::string and as such there is an object of type std::string associated with the variable. That object is initialised with the expression that is passed into the function as argument.

Also, if I observe that the output of d is still the same as prior to applying std::move, what does this mean in this case?

If you call the uncommented version of the function with an rvalue, then the argument will be moved from. If the value is same as it was, then it simply means that the value is the same. You cannot assume that the value will be the same nor that it won't be the same.

Does it mean that d is still occupying the space it originally occupied?

Assuming that by "space" you mean the storage where the variable is, then of course it is still occupying the same storage. The address of an object never changes through the lifetime of the object.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • Sorry, yes, I meant I was observing that `d` became empty based on the `cout` output, not that `std::move` ALWAYS makes it empty. I had thought if we had `std::string i`, a copy of the rvalue reference is made. I didn't think it actually invoked the move assignment operator. Is it safe to assume that a move assignment operator will always be invoked on non-user defined types if the RHS is an r-value? – 24n8 Apr 21 '20 at 14:00
  • Also, if I observe that the output of `d` is still the same as prior to applying `std::move`, what does this mean in this case? Does it mean that `d` is still occupying the space it originally occupied? – 24n8 Apr 21 '20 at 14:03
  • @Iamanon By "non-user defined types" do you mean "non-class"? Because only classes have move assignment operators. – eerorika Apr 21 '20 at 14:03
  • No, I just mean stuff we find in the C++ standard or in STL, so including standard classes. – 24n8 Apr 21 '20 at 14:04
0
void func(const std::string &&i)

This signature will not move anything, because the reference is to a const object. Remove the const, and it'll work. But only if you std::move the parameter i again inside the function. This is because anything that has a name is an lvalue, whether the parameter was declared as & or &&. See this answer.

void func(const std::string &i)

This will copy, as you probably already know. However, it behaves similarly to the ptevious one in that if you drop the const and do std::move( i ) inside the function, it'll actually move. This is because, as you noted, move is a cast and the compiler will listen to you and do exactly what you say when you cast, regardless of what you intended.

void func(const std::string i)

This moves in your example because here, i is an entirely new string. The outside string d gets moved into i. However, you still have to drop the const and use std::move( i ) if you want to move i into val.

Zuodian Hu
  • 979
  • 4
  • 9