rvalue for a std::string parameter
Asked Answered
P

2

11

What's the difference in practice between LVALUE and RVALUE in the following code when I pass the text? I mean, in this specific case of a string (where the string is a string literal), is there any benefit of using RVALUE (&&)?

void write_Lvalue(const std::string &text) {
    //...
}

void write_Rvalue(const std::string &&text) {
    //...
}

int main() {
    write_Lvalue("writing the Lvalue");
    write_Rvalue("writing the Rvalue");
}
Poltroon answered 10/4, 2019 at 18:56 Comment(1)
FWIW const std::string &&text should not be used. You defeat the purpose of the rvalue reference by making what it refers to const. If it is const it can't be moved from as that is a mutating operation.Brandeebranden
R
11

First, constant rvalue reference are not really useful, since you cannot move them. Moving value need mutable references to work.

Let's take your corrected example:

void write_lvalue(std::string const& text) {
    //...
}

void write_rvalue(std::string&& text) {
    //...
}

int main() {
    write_lvalue("writing the Lvalue");
    write_rvalue("writing the Rvalue");
}

In this case, the two are completely equivalent. In these two case, the compiler has to create a string and send it by reference:

int main() {
    // equivalent, string created
    // and sent by reference (const& bind to temporaries)
    write_lvalue(std::string{"writing the Lvalue"}); 

    // equivalent, string created
    // and sent by reference (&& bind to temporaries)
    write_rvalue(std::string{"writing the Rvalue"});
}

So why have function that takes rvalue references?

It depends on what you do with the string. A mutable reference can be moved from:

std::string global_string;

void write_lvalue(std::string const& text) {
    // copy, might cause allocation
    global_string = text;
}

void write_rvalue(std::string&& text) {
    // move, no allocation, yay!
    global_string = std::move(text);
}

So why using rvalue reference at all? Why not using mutable lvalue reference?

That is because mutable lvalue references cannot be bound to temporaries:

void write_lvalue_mut(std::string& text) {
    // move, no allocation... yay?
    global_string = std::move(text);
}

int main() {
    std::string s = /* ... */;
    write_lvalue_mut(std::move(s)); // fails
    write_lvalue_mut("some text"); // also fails
}

But mutable rvalue reference can be bound to rvalue, as shown above.

Refrigerator answered 10/4, 2019 at 19:15 Comment(3)
A mutable lvalue references also doesn't work with const lvalues, and are quite rude to users (who have to assume their variable might get changed by the function).Topcoat
In your second example, is std::move() even needed? text is already an rvalue, so std::string's operator=(string&&) method should be invoked anyway.Barrelchested
@EdwardFalk No, the expression text is an lvalue, even if the text variable was declared as a rvalue reference. You may want to revise value categories, because you definitely need that move.Refrigerator
P
4

There's no benefit in this case. write_Rvalue will only accept an rvalue. and write_Lvalue will only accept an lvalue.

When you pass a string literal a temporary std::string will be constructed from the string literal. The rvalue variant can already bind to this because you're already passing a temporary and the lvalue variant can bind to the temporary because it's const.

This for example, will not compile:

void write_Lvalue(const std::string &text) {
    //...
}

void write_Rvalue(const std::string &&text) {
    //...
}

int main() {
    std::string a = "hello";
    write_Rvalue(a);
}

because we're trying to pass an lvalue a to a function only accepting an rvalue.

The benefit that can be gained with rvalue types is that they can be moved from. There's a great post on why moving can be faster here.

Making your rvalue const defeats the purpose of it though as said in the comments, because it can't be moved from anymore.

Pediform answered 10/4, 2019 at 19:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.