Consider the following c++ programs:
string construct(string&& s) {
// Passing a r-value reference as an argument to the assignment operator
string constructed = s;
return constructed;
}
int main() {
string original = "Hello";
string temp1 = construct(std::move(original));
printf("%s\n", original.c_str()); // Prints "Hello", so original has not changed
return 0;
}
Now a small change that I perform is calling std::move on an r-value reference argument:
string constructWithMove(string&& s) {
// Passing a cast to an r-value reference using r-value reference as an argument.
string constructed = std::move(s);
return constructed;
}
int main() {
string original = "Hello";
string temp = constructWithMove(std::move(original));
printf("%s\n", original.c_str()); // Prints "", original is set to the empty string, WHY???
return 0;
}
So it looks like casting an r-value reference to an r-value reference induces something peculiar. Why in the first case the original string retains its value but not in the second?
What happens when std::move is called on a rvalue reference?
std::move casts the argument into an rvalue reference, which it returns.
Is std::move(r_value_reference_argument) undefined
No.
// Prints "", original is set to the empty string, WHY???
What's happening here?
Because the result from std::move is an r-value (x-value to be more specific). And being passed to a constructor of std::string, it invokes the move constructor. You are observing a string that was moved from.
Note that this moving leaves the original string in an unspecified state. It is not guaranteed to be empty, nor even different from what it used to contain. Neither is it guaranteed to not be empty.
OK, but why in the first case the original string is not empty?
Because s is an lvalue1, and therefore the copy constructor was used. The copy constructor leaves the original string unmodified.
1 Easy rule of thumb: If it is a name, then it is an l-value.
string constructed = s;
This does not cause a move because s is not an rvalue. It is an rvalue reference, but not an rvalue. If it has a name, it is not an rvalue. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.
string constructed = std::move(s);
This causes a move because std::move(s) is an rvalue: it's a temporary and its type is not lvalue reference.
There are no other moves in the program (std::move is not a move, it's a cast).
// Prints "", original is set to the empty string, WHY???
Because you moved it.
Related
Since we can pass rvalue to function taking const left ref,
void taking(const string& ref) {}
taking("abc");
can we return rvalue as const left ref without reporting warning?
const string& returning()
{
static string s = "abc";
if (1)
{
return s;
}
else
{
return "xyz"; // warning: return-local-addr
}
}
cout<<returning()<<endl;
The issue is not the value category (lvalue vs rvalue), but the fact that you return a reference at all.
It doesn't matter what kind of reference. When the function returns, the object that the return value is referencing, will be gone. In the case of return s; that is the local variable and in the case of return "xyz"; it is the temporary constructed in the return statement (which must be constructed because "xyz" itself is not a string and so cannot be bound to const string&).
You must return-by-value:
string returning()
If you do so, then an expression of the form returning() will be a prvalue (a kind of rvalue) and can be used e.g. in a function expecting a const lvalue reference, which can also bind to rvalues.
taking(returning()) // ok!
This is ok, because the return value of the function itself is not destroyed when the function exits (that would be nonsensical). Instead it lives until the end of the full expression in which the function was called.
There are also no unnecessary copies of the object being made due to copy elision rules (except for an unlikely extra move operation in return s;).
Is it allowed to return a moved value as a const lvalue reference?
include<string>
using namespace std;
class C {
private:
string s;
public:
const string &release() {
return move(s);
}
};
Well, yes but it won't do anything.
The std::move function is just a cast to a rvalue reference. So in effect:
std::string s;
std::move(s); // returns std::string&& to `s`
So it just returns a reference to the object you pass in.
So in your code, you create an rvalue reference to your string s, but you bind that reference to a std::string const&, which cannot be moved from.
You'd be better simply returning the reference directly:
const string &release() {
return s;
}
Or return by move (using exchange):
std::string release() {
return std::exchange(s, std::string{});
// s valid to be reused, thanks to std::exchange
}
The last solution would be to return an rvalue reference, but I wouldn't do that, as it won't guarantee the reference to be moved from.
No, and I don't see why you would want to. std::move is like a cast, to the rvalue-reference type. The point of rvalue references returned from std::move is for their corresponding values to be moved into another object efficiently, possibly changing the original object in the process. A const lvalue reference would not allow you to change the object, so it can't be moved from.
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.
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.
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.
I know this is valid in C++, because temporary from std::string("xxx") lives to the end of the full expression it appears in.
printf("%s\n", std::string("xxx").c_str());
And this is valid too:
std::string f1() {
return std::string("xxx");
}
void g1() {
printf("%s\n", f1().c_str());
}
But is this valid? if not, why?
const char* f2() {
return std::string("xxx").c_str();
}
void g2() {
printf("%s\n", f2());
// or: std::string x = f2(); // is this valid?
}
The temporary std::string inside of f2 is destroyed when the function returns. The char* will be dangling.
EDIT: In response to the comment, std::string x = f2() is also not valid. It's still bad to initialize a std::string from a dangling pointer.
The exact same rule applies. The full-expression in which the std::string was created in f2 is
std::string("xxx").c_str()
together with the construction of the return value from it.
After this expression executed and the construction of the return value executed, the temporary std::string("xxx") will be destroyed. Since destruction of the temporary std::string makes the const char* obtained from .c_str() and copied into the return value invalid, the return value of the function cannot be used in any way.
For f1 the same applies (details relating to temporary materialization and copy elision, especially in C++17, aside). The temporary std::string is destroyed after construction of the return value. But in this case this is not a problem, because the return value is not a pointer to something that was destroyed, but an independent std::string object itself. This std::string object will live until the end of the full-expression in which the call to f1 appears, i.e. until the end of
printf("%s\n", f1().c_str())
The following would be fine as well:
std::string f3() {
return std::string("xxx").c_str();
}
Here again, a new std::string is created in the return value, which will live until the end of the full-expression in which f3 is called.
In practice, for f1, since std::string("xxx"); is already a prvalue of type std::string, copy elision will be applied, so that there is really only one temporary object, the return value of f1. The intermediate temporary will be eliminated and the return value directly constructed from "xxx". This is mandatory since C++17 and was allowed and common before C++17.
I don't really understand why this is happening.
I have some functions which are declared like this:
std::string unmaskString(std::string &oValue);
In the code I do this:
v = unmaskString(line.substr(eq+1));
and I get a compile error saying:
error: invalid initialization of non-const reference of type 'std::string&' from a temporary of type 'std::basic_string<char, std::char_traits<char>, std::allocator<char> >'
When I put this in two separate statements it works:
v = line.substr(eq+1);
v = unmaskString(v);
The first line returns a string object, not even a reference, so I don't really understand the error.
Changing the function to
std::string unmaskString(std::string oValue);
also gives that error.
UPDATE:
Changed maskString to unmaskString as this was a mistake, but the problem still aplies as the masString has the same signature.
The result of:
line.substr(eq+1)
Is a temporary object of type std::string. Temporaries are rvalues, and lvalue references cannot bind to rvalues.
Notice, that if your maskString() function does not need to modify its argument (why would it return an std::string otherwise?), there is no reason for it to accept its argument as a reference to non-const.
The possible solutions are (in order of preference):
Let maskString() take its input by value, so that the input argument will be copied if it is an lvalue and moved if it is an rvalue:
std::string maskString(std::string oValue);
// ^^^^^^^^^^^
{
// Whatever is going on...
return oValue; // This will be MOVED into the object that stores
// the value returned by this function
}
Let maskString() take its input by lvalue reference to const (this way the initialization of value from oValue will always result in a copy, even if the argument is a temporary), then copy it into a temporary variable that will be eventually returned and moved from. This would work because lvalue references to const can bind to rvalues (and therefore to temporaries):
std::string maskString(std::string const& oValue);
// ^^^^^
{
std::string value = oValue;
// Whatever is going on...
return value; // This will be MOVED into the object that stores
// the value returned by this function
}
Do what you did: store the object returned by substr in a named object, and pass that object to unmaskString().