std::vector<std::string> func(int num) {
std::vector<std::string> vec;
int sum = 0;
for(int i = 0; i < num; i++) {
sum +=i;
std::string s = std::to_string(sum);
vec.emplace_back(s);
}
return vec;
}
what's the difference between the code below about using rvalue and lvalue reference.
std::vector<std::string> res = func(10); // (case 1)
std::vector<std::string> &&res = func(10); // (case 2)
std::vector<std::string> &res = func(10); // I got an error!, case3
const std::vector<std::string> &res = func(10); // case4
Question:
can the rvalue reference(case 2) save a memory copy? than case1
why lvalue reference(case3) got an error but it work with the const (case4)?
When a prvalue (a "temporary") is bound to an rvalue reference or a const lvalue reference, the lifetime of the temporary is extended to the lifetime of the reference.
a non-const lvalue reference does not extend the lifetime of a prvalue.
can the rvalue reference(case 2) save a memory copy? than case1
It preserves the lifetime of the single returned object. See 1.
why lvalue reference(case3) got an error
See 2.
but it work with the const (case4)?
See 1.
can the rvalue reference(case 2) save a memory copy? than case1
If you mean, "does case 2 avoid some copy that case 1 has", then the answer is: No. Both are effectively identical if res is an automatic variable. I recommend writing the case 1 because it is simpler and less confusing.
Firstly, there is a move from vec to the returned value. In practice, this move can be elided by an optimiser. Such optimisation is known as NRVO (Named Return Value Optimisation).
Since C++17: The is initialised directly by the move mentioned above (which may have been elided) in both cases.
Pre-C++17: There is another move from the return value to the initialised object in both cases. This move can also be elided in practice.
why lvalue reference(case3) got an error but it work with the const (case4)?
Because lvalue references to non-const may not be bound to rvalues.
Related
I am not sure about some rvalue/lvalue non-trvial examples.
are std::vector<int>({1,2,3})[0] and std::vector<int>() expressions below lvalue or rvalue?
the code has no actual usage but it surprises me a bit that the code is valid. It looks like they are both lvalues. or not?
#include <vector>
int main()
{
std::vector<int>({1,2,3})[0] = 0;
std::vector<int>() = {1,2,3};
return 0;
}
further example...
The std::vector<int>() expression below is a rvalue. right?
and how about the expression std::vector<int>({1,2,3})[0]?
Both the vector object from std::vector<int>() and the int value from ...[0] are temporary values. right?
auto v = std::vector<int>();
int i = std::vector<int>({1,2,3})[0];
std::vector<int>() is an rvalue, because the syntax type() always produces an rvalue. std::vector<int>() = {1,2,3}; is allowed because assigning to rvalues of class type is allowed in general, but can be disabled for a specific class by defining operator= with a & qualifier, which std::vector doesn't do.
std::vector<int>({1,2,3})[0] is an lvalue, because std::vector<...>::operator[] is defined to return an lvalue reference. It's possible to define it to return rvalue or lvalue depending on whether the vector it's called on an rvalue or lvalue (see the link above), but std::vector doesn't do that.
Both the vector object from std::vector() and the int value from ...[0] are temporary values. right?
The vector - yes. The int - no, at least formally.
Whether or not something an object is a temporary depends solely on the way it was created, e.g. type() will always give you a temporary.
The vector allocates its elements on the heap, which results in normal, non-temporary objects. It doesn't matter that the vector itself is a temporary.
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.
Code:
void test(int&& a)
{
a++;
std::cout << a << std::endl;
}
and execute:
test(0);
why output 1? Cause I think 0 is rvalue, it could not be changed.
If you bind a non-class, non-array prvalue, such as a numeric literal, to a reference, the reference will actually be bound to a temporary variable which is a copy of the original value. That is,
int&& a = 0;
creates a temporary int object with the value zero, and then binds a to that.
When you call test(0), the same rule applies, and the reference parameter is bound to a temporary; the temporary is incremented and you get the result 1. Of course you are not incrementing 0 itself.
Both rvalue references and lvalue references to const can bind to a temporary. The difference is, the former is modifiable, the latter is not.
int& i = 0; // invalid
const int& i = 0; // valid
++i; // invalid
int&& i = 0; //valid
++i; // valid;
Can someone verify that the following is a BUG, and explain why? I think I know, but am unclear about the details. (My actual problem involved a vector of enums, not ints, but I don't think it should matter.) Suppose I have the following code:
std::vector<int> f (void) {
std::vector<int> v;
v.push_back(5);
return v;
}
void g (void) {
const int &myIntRef = f()[0];
std::cout << myIntRef << std::endl;
}
Am I correct that myIntRef is immediately a dangling reference, because the return value of f is saved nowhere on the stack?
Also, is the following a valid fix, or is it still a bug?
const int myIntCopy = f()[0]; // copy, not a reference
In other words, is the return result of f() thrown away before the 0th element can be copied?
That is a bug. At the end of the complete expression const int &myIntRef = f()[0]; the temporary vector will be destroyed and the memory released. Any later use of myIntRef is undefined behavior.
Under some circumstances, binding a reference to a temporary can extend the lifetime of the temporary. This is not one of such cases, the compiler does not know whether the reference returned by std::vector<int>::operator[] is part of the temporary or a reference to an int with static storage duration or any other thing, and it won't extend the lifetime.
Yes, it is wrong thing to do indeed. When you call:
return v;
temporary copy of object v is being created and
const int &myIntRef = f()[0];
initializes your reference with the first element of this temporary copy. After this line, the temporary copy no longer exists, meaning that myIntRef is an invalid reference, using of which produces undefined behavior.
What you should do is:
std::vector<int> myVector = f();
const int &myIntRef = myVector[0];
std::cout << myIntRef << std::endl;
which (thanks to copy elision) uses an assignment operator to initialize myVector object by using v without copy of v being created. In this case the lifetime of your reference is equal to the lifetime of myVector object, making it perfectly valid code.
And to your second question:
"Also, is the following a valid fix, or is it still a bug?"
const int myIntCopy = f()[0]; // copy, not a reference
Yes, this is another possible solution. f()[0] will access the first element of the temporary copy and use its value to initialize myIntCopy variable. It is guaranteed that the copy of v returned by f() exists at least until the whole expression is executed, see C++03 Standard 12.2 Temporary objects ยง3:
Temporary objects are destroyed as the last step in evaluating the full-expression (1.9) that (lexically) contains the point where they were created.
Consider the below.
#include <string>
using std::string;
string middle_name () {
return "Jaan";
}
int main ()
{
string&& danger = middle_name(); // ?!
return 0;
}
This doesn't compute anything, but it compiles without error and demonstrates something that I find confusing: danger is a dangling reference, isn't it?
Do rvalue references allow dangling references?
If you meant "Is it possible to create dangling rvalue references" then the answer is yes. Your example, however,
string middle_name () {
return "Jaan";
}
int main()
{
string&& nodanger = middle_name(); // OK.
// The life-time of the temporary is extended
// to the life-time of the reference.
return 0;
}
is perfectly fine. The same rule applies here that makes this example (article by Herb Sutter) safe as well. If you initialize a reference with a pure rvalue, the life-time of the tempoary object gets extended to the life-time of the reference. You can still produce dangling references, though. For example, this is not safe anymore:
int main()
{
string&& danger = std::move(middle_name()); // dangling reference !
return 0;
}
Because std::move returns a string&& (which is not a pure rvalue) the rule that extends the temporary's life-time doesn't apply. Here, std::move returns a so-called xvalue. An xvalue is just an unnamed rvalue reference. As such it could refer to anything and it is basically impossible to guess what a returned reference refers to without looking at the function's implementation.
rvalue references bind to rvalues. An rvalue is either a prvalue or an xvalue [explanation]. Binding to the former never creates a dangling reference, binding to the latter might. That's why it's generally a bad idea to choose T&& as the return type of a function. std::move is an exception to this rule.
T& lvalue();
T prvalue();
T&& xvalue();
T&& does_not_compile = lvalue();
T&& well_behaved = prvalue();
T&& problematic = xvalue();
danger is a dangling reference, isn't it?
Not any more than if you had used a const &: danger takes ownership of the rvalue.
Of course, an rvalue reference is still a reference so it can be dangling as well. You just have to bring the compiler into a situation where he has to drag the reference along and at the same time you just escape the refered-to value's scope, like this:
Demo
#include <cstdio>
#include <tuple>
std::tuple<int&&> mytuple{ 2 };
auto pollute_stack()
{
printf("Dumdudelei!\n");
}
int main()
{
{
int a = 5;
mytuple = std::forward_as_tuple<int&&>(std::move(a));
}
pollute_stack();
int b = std::get<int&&>(mytuple);
printf("Hello b = %d!\n", b);
}
Output:
Dumdudelei!
Hello b = 0!
As you can see, b now has the wrong value. How come? We stuffed an rvalue reference to an automatic variable a into a global tuple. Then we escaped the scope of a and retrieve its value through std::get<int&&> which will evaluate to an rvalue-reference. So the new object b is actually move constructed from a, but the compiler doesn't find a because its scope has ended already. Therefore std::get<int&&> evaluates to 0 (although it is probably UB and could evaluate to anything).
Note that if we don't touch the stack, the rvalue reference will actually still find the original value of object a even after its scope has ended and will retrieve the right value (just try it and uncomment pollute_stack() and see what happens). The pollute_stack() function just moves the stack pointer forward and back while writing values to the stack by doing some io-related stuff through printf().
The compiler doesn't see through this though at all so be aware of this.