Working from the Efficient Modern C++, Item 25. we have an example
Case 1
class Widget {
public:
template<typename T>
void setName(T&& newName)
{ name = std::forward<T>(newName); }
...
};
Case 2
class Widget {
public:
void setName(const std::string& newName)
{ name = newName; }
void setName(std::string&& newName)
{ name = std::move(newName); }
...
};
The call
Widget w;
w.setName("Adela Novak");
Now assuming case 1, the book states that the literal is conveyed to the assignment operator for t std::string inside w's name data member.
Assuming case 2, the book states that -> first a temporary is created from the literal, calling the string constructor, so the setName parameter can bind to it, and than this temporary is moved into w's name data member.
Question
Why does this difference in behavior come about and how am I to think about it?
Namely, why is there no need for a temporary in case 1? Why is there difference? Is T&& not deduced to be an rvalue reference to a string, thus arriving at the same behavior as case 2 (obviously not, as per the book, but why)?
In case 1, T is deduced to be const char (&)[12], not std::string. There is no reason for the compiler to promote the string literal to std::string yet. In case 2, every overload requires takes a reference to an std::string, which forces the creation of a temporary std::string to which a reference can be bound using the implicit const char* constructor.
Note that while an rvalue reference such as std::string && may only bind to an rvalue, the templated equivalent T && may bind to both rvalues and lvalues.
Related
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?
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'm trying to understand the performance implications of using WidgetURef::setName (URef being a Universal Reference, the term coined by Scott Meyers) vs WidgedRRef::setName (RRef being an R-value Reference):
#include <string>
class WidgetURef {
public:
template<typename T>
void setName(T&& newName)
{
name = std::move(newName);
}
private:
std::string name;
};
class WidgetRRef {
public:
void setName(std::string&& newName)
{
name = std::move(newName);
}
private:
std::string name;
};
int main() {
WidgetURef w_uref;
w_uref.setName("Adela Novak");
WidgetRRef w_rref;
w_rref.setName("Adela Novak");
}
I do appreciate that with universal references one should be using std::forward instead, but this is just an (imperfect) example to highlight the interesting bit.
Question
In this particular example, what is the performance implications of using one implementation vs the other? Although WidgetURef requires type deduction, it's otherwise identical to WidgetRRef, isn't it? At least in this particular scenario, in both cases the argument is an r-value reference, so no temporaries are created. Is this reasoning correct?
Context
The example was taken from Item25 of Scott Meyers' "Effective Modern C++" (p. 170). According to the book (provided that my understanding is correct!), the version taking a universal reference T&& doesn't require temporary objects and the other one, taking std::string&&, does. I don't really see why.
setName(T&& newName) with argument "Adela Novak" gets T duduced as const char (&)[12] which is then assigned to std::string.
setName(std::string&& newName) with argument "Adela Novak" creates a temporary std::string object which is then move assigned to std::string.
The first one is more efficient here because there is no moving involved.
In this particular example, what is the performance implications of using one implementation vs the other?
Universal references, as Scott Meyers calls them, are not primarily there for performance reasons, but, loosely speaking, to treat both L- and Rvalue references in the same manner to avoid countless overloads (and for being able to propagate all type information during forwarding).
[...] so no temporaries are created. Is this reasoning correct?
Rvalue references do not prevent temporaries from being created. Rvalue references are the kind of references that are able to be bound to temporaries (apart from const lvalue references)! Of course, in your example, there will be temporaries, but the rvalue reference can bind to it. The universal reference first has to undergo the reference collapsing but in the end, the behaviour will be identical in your case:
// explicitly created temporary
w_uref.setName(std::string("Adela Novak"));
// will create temporary of std::string --> uref collapses to rvalue ref
// so is effectively the same as
w_rref.setName("Adela Novak");
By using the rvalue reference on the other hand, you force a temporary implicitly as std::string&& cannot bind to that literal.
w_rref.setName("Adela Novak"); // need conversion
So the compiler will create a temporary std::string from the literal the rvalue reference then can bind to.
I don't really see why.
In this case, the template will be resolved to const char(&)[12] and thus, no std::string temporary will be created in contrast to the case above. This therefore is more efficient.
Scott himself says that WidgetURef "compiles, but is bad, bad, bad!" (verbatim). These two classes behave differently as you use std::move instead of std::forward: setName therefore can modify its argument:
#include <string>
#include <iostream>
class WidgetURef {
public:
template<typename T>
void setName(T&& newName)
{
name = std::move(newName);
}
private:
std::string name;
};
int main() {
WidgetURef w_uref;
std::string name = "Hello";
w_uref.setName(name);
std::cout << "name=" << name << "\n";
}
can easily print name=, meaning that the value of name was changed. And indeed it does on ideone at the very least.
On the other hand, WidgetRRef requires that the passed argument is a rvalue-reference, so the example above wouldn't compile without explicit setName(std::move(name)).
Neither WidgetURef, nor WidgetRRef require creating extra copies if you pass std::string as an argument. However, if you pass something which std::string can be assigned from (such as const char*), then the first example will pass that by reference and assign it to string (without any copies except for copying data from C-style string into std::string), and the second example will first create a temporary string, and then pass it as an rvalue reference to the method. These properties preserve if you replace std::move(newName) with a correct std::forward<T>(newName).
Assuming the arguments as stated in the question
template<typename T>
void setName(T&& newName)
{
name = std::forward<T>(newName);
}
Will invoke the std::string assignment operator for the data member name with a const char * argument
void setName(std::string&& newName)
{
name = std::move(newName);
}
Invokes std::string constructor to create a temporary, to which the Rvalue Ref can bind to.
Invokes std::string move assignment / constructor for the data member name with a std::string&& argument
Invokes std::string destructor to destroy the temporary, from which we moved the data.
Constantness
class MyClass {
// ...
private:
std::string m_parameter;
// ...
}
Pass-by-value:
void MyClass::SetParameter(std::string parameter)
{
m_parameter = parameter;
}
Pass-by-ref:
void MyClass::SetParameter(std::string& parameter)
{
m_parameter = parameter;
}
Pass-by-const-ref:
void MyClass::SetParameter(const std::string& parameter)
{
m_parameter = parameter;
}
Pass-by-const-value:
void MyClass::SetParameter(const std::string parameter)
{
m_parameter = parameter;
}
Pass-by-universal-ref:
void MyClass::SetParameter(std::string&& parameter)
{
m_parameter = parameter;
}
Pass-by-const-universal-ref:
void MyClass::SetParameter(const std::string&& parameter)
{
m_parameter = parameter;
}
Which variant is the best (possibly in terms of C++11 and its move semantics)?
PS. May be bodies of the functions is in some cases incorrect.
Pass by value: not good in general as a value copy might be taken. (Although a move constructor might mitigate).
Pass by reference: not good as the function might modify the parameter passed. Also an anonymous temporary cannot bind to a reference.
Pass by const reference: still the best. No copy taken, function cannot modify the parameter, and an anonymous temporary can bind to a const reference.
Passing by && variants: Currently pointless, as there are no move semantics given the way you've written the function bodies. If you'd written std::move(m_parameter, parameter) in place of the assignment then this might win over (3) in some cases, and the compiler will pick the better.
See 'Effective C++' Scott Meyers - If the private member is a built in type then it can be more efficient to pass by value than pass by reference (which are typically implemented as pointers in the compiler). This is also true for iterators and function objects in STL which are designed to be passed by value. Otherwise pass by reference-to-const is preferable.