This may be obvious but I think it is something difficult to me. Given this:
void test(std::string&&) { }
std::string x{"test"};
test(std::move(x)); // ok
This code calls test() with a rvalue reference as parameter so the program compiles as I expect.
Now look at this:
void other_test(const std::string&) { }
std::string x{"test"};
other_test(std::move(x)); // ok???
And here I'm tilted. Why does this version compile? The std::move returns a && type; why then I don't get an error in the second method where I use const&?
I know that
int&& s = 5;
const int& s = 5;
is valid because in both cases I provide something that has not an lvalue, it has no addresses. Are && and const& equivalent? If no, are there differences?
std::move doesn't actually move anything out of it's own. It's just a fancy name for a cast to a T&&. Calling test like this test(std::move(x)); only shows that a T&& is implicitly convertible to a const T&. The compiler sees that test only accepts const T& so it converts the T&& returned from std::move to a const T&, that's all there is to it.
In simple terms:
&& can bind to non-const rvalues (prvalues and xvalues)
const && can bind to rvalues (const and non-const)
& can bind to non-const lvalues
const & can bind to rvalues (prvalues and xvalues) and lvalues (const and non-const for each). A.k.a. to anything.
If you want a function to expressly allow const-Lvalue objects, but expressly disallow Rvalue objects, write the function signature like this:
void test(const std::string&) { }
void test(std::string&&) = delete;//Will now be considered when matching signatures
int main() {
std::string string = "test";
test(string);//OK
//test(std::move(string));//Compile Error!
//test("Test2");//Compile Error!
}
test(std::string&& a) {
something(a) //--> not moved because it has lvalue
Names of variables are lvalues. a is a name of a variable, therefore a is an lvalue expression, and therefore it will not be moved from.
It's unclear what you mean by "has". a is an expression. It is a name of a reference, and references refer to objects. Value categories pertain to expressions, not objects.
test(const std::string& a): a is const lvalue reference and like before I have lvalue and rvalue. And plus more, in this case if I called
std::move(a)
where a is a const& the move works!
If by "works" you mean that it invokes a move constructor or assignment, then no, it does not work because no move construction or assignment has happened.
When you call std::move(x), an rvalue reference to the underlying data, test, will be returned. You are allowed to pass rvalue references as const (and const only!) reference parameters because an rvalue reference is implicitly convertible to a const reference. They are arguably the same thing from the function's point of view (a read only parameter). If you removed the const-qualifier of your parameter, this code would not compile:
void other_test(std::string&) { }
std::string x{"test"};
other_test(std::move(x)); //not okay because
//the function can potentially modify the parameter.
See Bo Qian's youtube video on rvalue vs lvalue.
Related
Passing lvalue reference as an rvalue reference argument does not compile. Compiler could create a temporary object with its copy constructor and pass it as an rvalue, but it does not. Still, it does call the constructor, if types do not match.
I'm interested, why does it works that way? Which design logic is here in the C++ standard, that forces the compiler to treat copy-constructor in a different way?
void do_smth(string && s)
{}
void f(const char * s)
{
do_smth(s); // passes a temporary string constructed from const char*
}
void g(const string & s)
{
do_smth(s); // does not compile
}
void h(const string & s)
{
do_smth(string(s)); // ok again
}
What should I do if I do not want to implement the second signature do_smth(const string &)? Should I use pass-by-value void do_smth(string s) instead?
What are other differences betweeen by-value void do_smth(string s) and rvalue-reference void do_smth(string && s), given the object string has move constructor?
This is the whole point of rvalue references. They bind to rvalues, and not to lvalues.
That is how you ensure that the overload you wanted is invoked, when you have both an lvalue version and an rvalue version. For example, a copy constructor vs a move constructor.
It's a feature.
Also, your lvalue is const, which does not match the signature of do_smth even if it did not take an rvalue reference.
If you want do_smth to be able to take either kind of expression, either make it take a const string&, or make it a template and have it take a forwarding reference T&& (which looks like an rvalue reference but kind of isn't).
If you want do_smth to store its own version of the string then have it take a value: you can still avoid a copy if needs be by using std::move at the callsite (or by passing an rvalue, which will trigger the string's own move constructor).
See how all options are available and elegant, due to how the rvalue reference binding rules are made? It all fits together.
Deciding what to allow do_smth to take entirely depends on what "smth" it is going to "do", and only you can say what that is.
in h you give a non const copy of the const string, so the const disappears contrarily to the g case
of course with void do_smth(string s){...} the copy is made at the call and both const and non const string can be given in argument
(A)
std::string doNothing(const std::string&& s)
{
return std::move(s);
}
(B)
std::string doNothing(const std::string& s)
{
return std::move(s);
}
(Test)
const std::string str = "aaaaaa";
const auto str2 = doNothing(str);
two questions:
are (A) and (B) differetnt?
in (Test): will str be undefined? after it is moved to str2?
std::move takes a (non-const) rvalue reference as an argument. So you can't bind it directly to a const anything, whether rvalue or not. So in both (A) and (B), when you call std::move, it makes an unnamed temporary copy and moves that.
As a result, str is never moved from, and is never affected by any of this.
To get rid of the const without making a copy, you need an explicit const_cast. If you did that, you'd get the obvious undefined behavior.
they are different. in A case, function accepts only rvalues of type const string, it doesn't accept lvalues, since you can not bind lvalue to rvalue reference. the function from B case accepts both rvalues and lvalues, since you can bind rvalue to const lvalue reference. inside their bodies they are equivalent, unless you are doing decltype (parameter).
again, there is std::string( const std::string & ) copy constructor, that can accept rvalue string object (std::move( s ) returns const std::string && and copying occurs).
With the given test code, the cases are different because A fails to compile and B does compile.
In case A an rvalue reference cannot bind to an lvalue.
In case B the function returns a copy of the string. Using std::move with a const reference still yields a const reference.
I was wondering about a c++ behaviour when an r-value is passed among functions.
Look at this simple code:
#include <string>
void foo(std::string&& str) {
// Accept a rvalue of str
}
void bar(std::string&& str) {
// foo(str); // Does not compile. Compiler says cannot bind lvalue into rvalue.
foo(std::move(str)); // It feels like a re-casting into a r-value?
}
int main(int argc, char *argv[]) {
bar(std::string("c++_rvalue"));
return 0;
}
I know when I'm inside bar function I need to use move function in order to invoke foo function. My question now is why?
When I'm inside the bar function the variable str should already be an r-value, but the compiler acts like it is a l-value.
Can somebody quote some reference to the standard about this behaviour?
Thanks!
str is a rvalue reference, i.e. it is a reference only to rvalues. But it is still a reference, which is a lvalue. You can use str as a variable, which also implies that it is an lvalue, not a temporary rvalue.
An lvalue is, according to §3.10.1.1:
An lvalue (so called, historically, because lvalues could appear on the left-hand side of an assignment expression) designates a function or an object. [ Example: If E is an expression of pointer type, then *E is an lvalue expression referring to the object or function to which E points. As another example, the result of calling a function whose return type is an lvalue reference is an lvalue. —end example ]
And an rvalue is, according to §3.10.1.4:
An rvalue (so called, historically, because rvalues could appear on the right-hand side of an assignment
expression) is an xvalue, a temporary object (12.2) or subobject thereof, or a value that is not associated with an object.
Based on this, str is not a temporary object, and it is associated with an object (with the object called str), and so it is not an rvalue.
The example for the lvalue uses a pointer, but it is the same thing for references, and naturally for rvalue references (which are only a special type of references).
So, in your example, str is an lvalue, so you have to std::move it to call foo (which only accepts rvalues, not lvalues).
The "rvalue" in "rvalue reference" refers to the kind of value that the reference can bind to:
lvalue references can bind to lvalues
rvalue references can bind to rvalues
(+ a bit more)
That's all there's to it. Importantly, it does not refer to the value that get when you use the reference. Once you have a reference variable (any kind of reference!), the id-expression naming that variable is always an lvalue. Rvalues occur in the wild only as either temporary values, or as the values of function call expressions, or as the value of a cast expression, or as the result of decay or of this.
There's a certain analogy here with dereferencing a pointer: dereferencing a pointer is always an lvalue, no matter how that pointer was obtained: *p, *(p + 1), *f() are all lvalues. It doesn't matter how you came by the thing; once you have it, it's an lvalue.
Stepping back a bit, maybe the most interesting aspect of all this is that rvalue references are a mechanism to convert an rvalue into an lvalue. No such mechanism had existed prior to C++11 that produced mutable lvalues. While lvalue-to-rvalue conversion has been part of the language since its very beginnings, it took much longer to discover the need for rvalue-to-lvalue conversion.
My question now is why?
I'm adding another answer because I want to emphasize an answer to the "why".
Even though named rvalue references can bind to an rvalue, they are treated as lvalues when used. For example:
struct A {};
void h(const A&);
void h(A&&);
void g(const A&);
void g(A&&);
void f(A&& a)
{
g(a); // calls g(const A&)
h(a); // calls h(const A&)
}
Although an rvalue can bind to the a parameter of f(), once bound, a is now treated as an lvalue. In particular, calls to the overloaded functions g() and h() resolve to the const A& (lvalue) overloads. Treating a as an rvalue within f would lead to error prone code: First the "move version" of g() would be called, which would likely pilfer a, and then the pilfered a would be sent to the move overload of h().
Reference.
We can't bind non-const lvalue reference to an rvalue, but it can be bound to the const one. We can't bind rvalue reference to an lvalue also. Actually the Standard say so:
8.5.3/5.2:
the reference shall be an lvalue reference to a non-volatile const
type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.
But is there a better explanation for the things than "The Standard say so"?
Because it doesn't make semantic sense.
You can't bind a non-const lvalue reference to an rvalue, because what does it mean to modify an rvalue? By definition nothing else will see the result, so this doesn't make sense.
int& i = 3;
//should change referenced location, but we aren't referencing a memory location
i = 5;
You can't bind an rvalue reference to an lvalue because rvalue references exist to facilitate destructive optimizations on their referencee. You wouldn't want your objects to be arbitrarily moved out from under you, so the standard doesn't allow it.
void process_string (std::string&&);
std::string foo = "foo";
//foo could be made garbage without us knowing about it
process_string (foo);
//this is fine
process_string (std::move(foo));
Think to some real cases:
#include <vector>
void f1( int& i ){i = 1;}
void f2( const int& i ){i = 1;}
void f3( std::vector<int>&& v ){auto v_move{v};}
int main()
{
f1(3); // error: how can you set the value of "3" to "1"?
f2(3); // ok, the compiler extend the life of the rvalue "into" f2
std::vector<int> v{10};
f3(v); // error: an innocent looking call to f3 would let your v very different from what you would imagine
f3(std::vector<int>{10}); // ok, nobody cares if the rvalue passed as an argument get modified
}
Why is this code well-formed? I'm not passing a reference to the function:
void function(const int& ref) {
}
int main()
{
function(1);
}
Constant lvalue references can bind to rvalues. Rvalues, like your literal 1, don't have a persistent alias, so if you were to modifying it, you wouldn't be able to observe the effect, but if you promise not to modify it (namely by accessing it through a constant reference), you can still have perfectly sensible code, and that's why this binding is allowed.
(You can also bind rvalues to (mutable) rvalue references: void function(int &&) In that case, the rvalue reference becomes the (unique) alias of the value.)
Note also that without this rule it would be impossible to initialize variables from functions that return prvalues, or use copy-initialization at all:
struct T { T(int); };
T f();
T x = 1; // === "T x = T(1);", copy constructor wants to bind to prvalue
T x = f(); // ditto
T x((f())); // ditto
The compiler can create a temporary from the constant and temporaries are allowed to bind to const references. If the reference wasn't const, this wouldn't be allowed.