I have a function f that takes a string as input. I usually want to provide a string literal, e.g., f("hello"). However, I want to implement another function g that builds upon f:
std::string f(const std::string&& x) {
return x + " world";
}
std::string g(const std::string&& x) {
std::string res = f(x); // problem: rvalue reference to std::string cannot bind to lvalue of type std::string
res += "!";
return res;
}
int main() {
std::string res_a = f("hello");
std::string res_b = g("world");
return 0;
}
How can I achieve this in C++11/14 in a way that I can use f with string literals as well as variables?
A generic way of solving the problem of a function taking both l-value and r-value references is to use templated functions like so-
template <typename T>
T f(T&& val) {
}
template <typename T>
T g(T&& val) {
T some_val = f(std::forward<T>(val));
}
std::foward<T>(val) forwards an l-value as an l-value and an r-value as an r-value, just as its name implies.
By templating the function, you ensure that this logic works for any type and not just strings.
The traditional way to take a read-only parameter is by const lvalue reference.
std::string f(const std::string& x)
This rule of thumb applies to many types, not just std::string. The primary exceptions are types that are not bigger than a pointer (e.g. a char).
It's rather unusual for a function to have a const rvalue reference. As you discovered, that adds difficulty when trying to pass a variable as the argument. A non-const rvalue reference has value, but a const rvalue reference is inferior to a const lvaue reference in most cases. See also Do rvalue references to const have any use?
Related
In Item 41, Scott Meyers writes the following two classes:
class Widget {
public:
void addName(const std::string& newName) // take lvalue;
{ names.push_back(newName); } // copy it
void addName(std::string&& newName) // take rvalue;
{ names.push_back(std::move(newName)); } // move it; ...
private:
std::vector<std::string> names;
};
class Widget {
public:
template<typename T> // take lvalues
void addName(T&& newName) // and rvalues;
{ // copy lvalues,
names.push_back(std::forward<T>(newName)); } // move rvalues;
} // ...
private:
std::vector<std::string> names;
};
What's written in the comments is correct, even if it doesn't mean at all that the two solutions are equivalent, and some of the differences are indeed discussed in the book.
In the errata, however, the author comments another difference not discussed in the book:
Another behavioral difference between (1) overloading for lvalues and rvalues and (2) a template taking a universal reference (uref) is that the lvalue overload declares its parameter const, while the uref approach doesn't. This means that functions invoked on the lvalue overload's parameter will always be the const versions, while functions invoked on the uref version's parameter will be the const versions only if the argument passed in is const. In other words, non-const lvalue arguments may yield different behavior in the overloading design vis-a-vis the uref design.
But I'm not sure I understand it.
Actually, writing this question I've probably understood, but I'm not writing an answer as I'm still not sure.
Probably the author is saying that when a non-const lvalue is passed to addName, newName is const in the first code, and non-const in the second code, which means that if newName was passed to another function (or a member function was called on it), than that function would be required to take a const parameter (or be a const member function).
Have I interpreted correctly?
However, I don't see how this makes a difference in the specific example, since no member function is called on newName, nor it is passed to a function which has different overloads for const and non-const parameters (not exactly: std::vector<T>::push_back has two overloads for const T& arguments and T&& arguments`, but an lvalue would still bind only to the former overload...).
In the second scenario, when a const std::string lvalue is passed to the template
template<typename T>
void addName(T&& newName)
{ names.push_back(std::forward<T>(newName)); }
the instantiation results in the following (where I've removed the std::forward call as it is, in practice, a no-op)
void addName(const std::string& newName)
{ names.push_back(newName); }
whereas if a std::string lvalue is passed, then the resulting instance of addName is
void addName(std::string& newName)
{ names.push_back(newName); }
which means that the non-const version of std::vector<>::push_back is called.
In the first scenario, when a std::string lvalue is passed to addName, the first overload is picked regardless of the const-ness
void addName(const std::string& newName)
{ names.push_back(newName); }
which means that the const overload of std::vector<>::push_back is selected in both cases.
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");
}
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.
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.
I have a function which accepts a std::string&:
void f(std::string& s) { ... }
I have a const char* which should be the input parameter for that function. This works:
const char* s1 = "test";
std::string s2{s};
f(s2);
This doesn't:
const char* s1 = "test";
f({s1});
Why isn't this possible? The funny thing is that CLion IDE is not complaining, but the compiler is:
no known conversion for argument 1 from ‘<brace-enclosed initializer list>’ to ‘std::basic_string<char>&’
This has nothing to do with constructing std::string from char const*.
f expects a lvalue to a string, and by creating a temporary instance on the spot, you're providing an rvalue, which cannot be bound to a non-const lvalue reference. f(string{}) is just as invalid.
Your function receives a non const reference and you are passing a temporary object, which requires a copy or a const reference parameter. Two solutions, creating another function to receive the object as a rvalue reference and call the other overload within
void f(string&& s) { f(s); }
to allow temporary objects as parameter, or change your function definition to receive any object but as a constant reference
void f(const std::string& s) { ... }
One option is to change your function to take a string by value, not by reference. Then it will work. In any case, in C++11 sometimes it's preferable to pass by value, not by reference.
The definition of xvalue is as follows:
— An xvalue (an “eXpiring” value) also refers to an object, usually near the end of its lifetime (so that its resources may be moved, for example). An xvalue is the result of certain kinds of expressions involving rvalue references (8.3.2). [ Example: The result of calling a function whose return type is an rvalue reference is an xvalue. —end example ]
Will we ever fall into where we practically need to use a function whose return type is an rvalue reference, which is an xvalue?
const int && Foo()
{
// ...
}
Move semantics take an rvalue reference as a parameter, not a return value. So I don't think that's the case.
Returning rvalue references can be of use for functions that already take rvalues as parameters. A simple example:
struct X {
X() = default;
X(X&& other) { std::cout << "move ctor\n"; }
X(X const&) = delete;
void log(std::string const& s){ std::cout << "log: " << s << "\n"; }
};
void sink(X&& x) {
x.log("sink");
}
X&& passOn(X&& in) {
in.log("pass");
return std::move(in);
}
X moveOn(X&& in) {
in.log("move");
return std::move(in);
}
int main() {
sink(passOn(X()));
std::cout << "===============================\n";
sink(moveOn(X()));
}
Live demo →
The second function will call the move constructor to create the returned object, while the first will pass on the reference it already got. This is more useful if we don't return the original reference but instead a reference to a part of the referred object, e.g.
template<class T>
T&& getHead(std::vector<T>&& input) {
return std::move(input.front());
}
That's exactly what std::move is — the result of std::move execution is an xvalue. Other than that it is hard to tell since in the main returning a reference from the function is a bad thing most of the time. But maybe someone will come up with another clever usage of such a function.
Will we ever fall into where we practically need to use a function whose return type is an rvalue reference, which is an xvalue?
It used in container classes, for instance tuple has a get overload that looks like this:
template< std::size_t I, class... Types >
typename std::tuple_element<I, tuple<Types...> >::type&&
get( tuple<Types...>&& t );
I assume that std::optional and std::variant in C++17 will both have a similar overloads.
Granted, the only point is to avoid to type std::move in some very specific situations, like:
auto x = std::get<1>( f() );
Where f returns a tuple by value.
Motivation:
I have a function
func(const string& s);
{
//...
}
of course this wont work:
func("hello" + " " + "world");
this solves that problem:
func(string&& s);
Is there a way to write func so that it automagically works with both r-value refs and l-value refs?
Rvalues bind to const lvalue refs with no problem. The problem with your code lies in types. You need at least one std::string for the overloaded operator+ to be used.
func(std::string("hello") + " " + "world");
Yes. You can use the reference collapsing behavior of templates:
template <typename S>
func (S && s)
{
//...
}
This preserves l- and r-valueness as well as constness.