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.
Related
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.
I am puzzled by the following code:
#include <iostream>
int main()
{
int x{};
int&& rvx = static_cast<int&&>(x);
++rvx;
std::cout << x << std::endl;
}
The output of it is 1. I don't understand how this works. The static_cast is supposed to cast the lvalue x into an xvalue, which is then assigned to rvx. Why does incrementing rvx lead to a change in x? Is this because the converted lvalue-to-rvalue is essentially sitting at the same memory location, but it is just considered now a rvalue? I was under the impression (which is probably false) that somehow the cast creates a temporary out of its argument.
rvalue reference is a reference. In this, it works just like any other reference.
An rvalue reference can bind to a temporary. This is what you'd get, for instance, if you write
int x{};
int&& rvx = +x;
But it doesn't need to bind to a temporary. By casting x to int&&, you've indicated to the compiler that that's okay too, that it may treat x as an rvalue to bind directly to the reference.
If I have the following code, is the vector copied?
std::vector<int> x = y.getTheVector();
or would it depend on whether the return type of getTheVector() is by reference?
or would I just need to use:
std::vector<int>& x = y.getTheVector();
or, would I need to do both?
std::vector<int> x = y.getTheVector();
always makes a copy, regardless of the return type of y.getTheVector();.
std::vector<int>& x = y.getTheVector();
would not make a copy. However, x will be valid as long as y.getTheVector() returns a reference to an object that is going to be valid after the function returns. If y.getTheVector() returns an object created in the function, x will point to an object that is no longer valid after the statement.
std::vector<int> x = y.getTheVector();
This is copy-initialization. There are three possible scenarios:
The return value of getTheVector() is a lvalue reference. In this case, the copy constructor is always invoked.
The return value of getTheVector() is a temporary. In this case, the move constructor may be called, or the move/copy may be completely elided by the compiler.
The return value is a rvalue reference (usually a terrible idea). In this case, the move constructor is called.
For this line,
std::vector<int>& x = y.getTheVector();
This only compiles if getTheVector returns a lvalue reference; a temporary cannot be bound a non-const lvalue reference. In this case, no copy is ever made; but the lifetime problem may be tricky.
std::vector<int> x = y.getTheVector();
Your first example does indeed copy the vector, regardless of whether the "getTheVector" function returns a vector or a reference to a vector.
std::vector<int>& x = y.getTheVector();
In your second example, however, you are creating a reference, so the vector will NOT be copied.
Wrong form:
int &z = 12;
Correct form:
int y;
int &r = y;
Question:
Why is the first code wrong? What is the "meaning" of the error in the title?
C++03 3.10/1 says: "Every expression is either an lvalue or an rvalue." It's important to remember that lvalueness versus rvalueness is a property of expressions, not of objects.
Lvalues name objects that persist beyond a single expression. For example, obj , *ptr , ptr[index] , and ++x are all lvalues.
Rvalues are temporaries that evaporate at the end of the full-expression in which they live ("at the semicolon"). For example, 1729 , x + y , std::string("meow") , and x++ are all rvalues.
The address-of operator requires that its "operand shall be an lvalue". if we could take the address of one expression, the expression is an lvalue, otherwise it's an rvalue.
&obj; // valid
&12; //invalid
int &z = 12;
On the right hand side, a temporary object of type int is created from the integral literal 12, but the temporary cannot be bound to non-const reference. Hence the error. It is same as:
int &z = int(12); //still same error
Why a temporary gets created? Because a reference has to refer to an object in the memory, and for an object to exist, it has to be created first. Since the object is unnamed, it is a temporary object. It has no name. From this explanation, it became pretty much clear why the second case is fine.
A temporary object can be bound to const reference, which means, you can do this:
const int &z = 12; //ok
C++11 and Rvalue Reference:
For the sake of the completeness, I would like to add that C++11 has introduced rvalue-reference, which can bind to temporary object. So in C++11, you can write this:
int && z = 12; //C+11 only
Note that there is && intead of &. Also note that const is not needed anymore, even though the object which z binds to is a temporary object created out of integral-literal 12.
Since C++11 has introduced rvalue-reference, int& is now henceforth called lvalue-reference.
12 is a compile-time constant which can not be changed unlike the data referenced by int&. What you can do is
const int& z = 12;
Non-const and const reference binding follow different rules
These are the rules of the C++ language:
an expression consisting of a literal number (12) is a "rvalue"
it is not permitted to create a non-const reference with a rvalue: int &ri = 12; is ill-formed
it is permitted to create a const reference with a rvalue: in this case, an unnamed object is created by the compiler; this object will persist as long as the reference itself exist.
You have to understand that these are C++ rules. They just are.
It is easy to invent a different language, say C++', with slightly different rules. In C++', it would be permitted to create a non-const reference with a rvalue. There is nothing inconsistent or impossible here.
But it would allow some risky code where the programmer might not get what he intended, and C++ designers rightly decided to avoid that risk.
References are "hidden pointers" (non-null) to things which can change (lvalues). You cannot define them to a constant. It should be a "variable" thing.
EDIT::
I am thinking of
int &x = y;
as almost equivalent of
int* __px = &y;
#define x (*__px)
where __px is a fresh name, and the #define x works only inside the block containing the declaration of x reference.
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.