std::move return and input reference argument - c++

(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.

Related

Why can we not use move semantics only apply for pass-by-value and not const lvalue reference?

Here is the article I am using as a reference, which was ultimately mentioned in this SO answer.
The author gives two examples:
Example 1:
std::vector<std::string>
sorted(std::vector<std::string> names)
{
std::sort(names);
return names;
}
// names is an lvalue; a copy is required so we don't modify names
std::vector<std::string> sorted_names1 = sorted( names );
// get_names() is an rvalue expression; we can omit the copy!
std::vector<std::string> sorted_names2 = sorted( get_names() );
Example 2:
std::vector<std::string>
sorted2(std::vector<std::string> const& names) // names passed by reference
{
std::vector<std::string> r(names); // and explicitly copied
std::sort(r);
return r;
}
Then saying:
Although sorted and sorted2 seem at first to be identical, there could
be a huge performance difference if a compiler does copy elision. Even
if the actual argument to sorted2 is an rvalue, the source of the
copy, names, is an lvalue, so the copy can’t be optimized away. In a
sense, copy elision is a victim of the separate compilation model:
inside the body of sorted2, there’s no information about whether the
actual argument to the function is an rvalue; outside, at the call
site, there’s no indication that a copy of the argument will
eventually be made.
My question is simple:
Why cannot the compiler use copy elision in the second example, but it can in the first?
What about passing by value and reference differentiates them? names is named in both instances, so I would assume we also create an lvalue in both instances.
The difference is that names modifiable object or not. const lvalue reference is not modifiable.
since get_names() is a rvalue, we can use move semantics.
Not always. Consider when the move constructor is called.
However if the parameter is a const lvalue reference, then we must "preserve" the value as an lvalue, so a simple switch of pointers cannot occur
When the parameter is lvalue reference, you can modify the object. However, you cannot pass an rvalue.
std::vector<std::string>
sorted3(std::vector<std::string>& names)
{
std::sort(names);
return names;
}
// names is an lvalue; lvalue reference can bind lvalue. argument will be modified after execution `sorted3`.
std::vector<std::string> sorted_names1 = sorted3( names );
// get_names() is an rvalue expression; we cannot bind by lvalue reference.
//std::vector<std::string> sorted_names2 = sorted3( get_names() );
const lvalue reference can bind rvalue. However, you cannot modify the object so that a simple switch of pointers cannot occur.

Why passing an lvalue as an rvalue argument fails?

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

C++11 rvalue reference vs const reference

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.

r-value parameters in a function

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.

Returning by value to rvalue reference

I'm studying rvalue references and I have a doubt in the following code:
string func() {
return "Paul";
}
int main()
{
string&& nodanger = func();
// The lifetime of the temporary is extended
// to the life-time of the reference.
return 0;
}
The question is: what does func() return?
I believe this is what happens:
func returns a prvalue "Paul" (is this a const char * due to a rvalue->pointer conversion?)
a string object is implicitly constructed (which ctor is used?)
due to reference collapsing rules it is bound to "nodanger" (does this behave any differently from a string& normal reference?)
Your func() function returns an std::string prvalue. The constructor being used to construct the std::string is
basic_string( const CharT* s,
const Allocator& alloc = Allocator() );
This prvalue is bound to the rvalue reference nodanger, which extends its lifetime to match that of the reference itself. Reference collapsing doesn't come into play here.
does this behave any differently from a string& normal reference?
The code wouldn't compile if nodanger was a string& because you can't bind rvalues to non-const lvalue references. The lifetime extension behavior in your example is identical to the following case
std::string const& nodanger = func();
Definitely lots of confusion here. I'll assume middle_name() is supposed to be func().
The question is: what does func() return?
It returns a string (which I will assume is a std::string). This return object is initialised with the expression "Paul" which has type "array of 5 const char". The constructor used is the following:
basic_string( const CharT* s,
const Allocator& alloc = Allocator() );
To call this function, the string literal has to undergo implicit array-to-pointer conversion (taking us from a const char[5] to a const char*.
The expression func() is a prvalue expression (a subset of rvalue expressions) because it returns by value. Here's how the standard defines this:
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
It doesn't make sense to say "returns a prvalue". A "prvalue" is not an object or a type. It's a category of expression. The expression which calls func is a prvalue.
The rvalue reference binds to the std::string object that has been returned from the function. There's no reference collapsing going on here. If the reference were an lvalue reference instead, your code wouldn't compile because a non-const lvalue reference can't bind to an rvalue expression.