I have been studying Effective Modern C++11 and 14 recently.
I went through a part of the book located on page 171, item 25, where the author provides two code examples for the setName function.
(1)
// Option 1, universal reference
class Widget {
public:
template<typename T>
void setName(T &&newName) // universal reference
{ name = std::move(newName); }// compiles, but is // bad, bad, bad!
//…
private:
std::string name;
std::shared_ptr<SomeDataStructure> p;
};
void main() {
Widget w;
w.setName("Adela Novak");
}
(2)
// Option 2, lvalue reference and rvalue reference
class Widget2 {
public:
void setName(const std::string &newName) { name = newName; }
void setName(std::string &&newName) { name = std::move(newName); }
//…
};
void main() {
Widget2 w;
w.setName("Adela Novak");
}
The author said:
With the version of setName taking a universal reference, the string literal "Adela Novak" would be passed to setName, where it would be conveyed to the assignment operator for the std::string inside w. w’s name data member would thus be assigned directly from the string literal; no temporary std::string objects would arise.
With the overloaded versions of setName, however, a temporary std::string object would be created for setName’s parameter to bind to, and this temporary std::string would then be moved into w’s data member. A call to setName would thus entail execution of one std::string constructor (to create the temporary), one std::string move assignment operator (to move newName into w.name), and one std::string destructor (to destroy the temporary).
If my understanding is correct, in both option 1 and option 2, we will create a temporary std::string object for “Adela Novak” implicitly in main() function.
For option 1, we send that temporary std::string object to setName(), and then T&& will be deduced as rvalue because “Adela Novak” is rvalue.
Hence, in option 1, we only create one temporary std::string object.
For option 2, we send that temporary std::string object to setName(). Because “Adela Novak” is rvalue, so we will pick up the rvalue overload setName() function. But, when we are going to call rvalue overload function, we have to create another temporary std::string object explicitly for newName.
Hence, in option 2, we have to create two temporary std::string objects.
My question is:
Updated: I asked my question to ChatGPT. However, it seems like the answer ChatGPT provided not 100% author's answer...
In Option 1, when the string literal "Adela Novak" is passed to the setName() function, the type deduction of T results in T being deduced as a const char* pointer. Since T is a non-reference type, newName is declared as an rvalue reference (
T&&
).When the rvalue reference
newName
is passed to thestd::move()
function, it performs a cast to an rvalue, which allows it to bind to the move constructor of thestd::string
class. Therefore, a temporarystd::string
object is created using thestd::string
constructor that takes aconst char*
argument.This temporary
std::string
object is then moved to thename
data member using the move assignment operator, resulting in no further copies of the string data.So, in Option 1, one temporary
std::string
object is created to hold the string data, but no additional temporary objects are created.
In Option 2, when the string literal "Adela Novak" is passed to the
setName()
function, the compiler tries to find the best match for the argument.The first
setName()
function taking aconst std::string&
parameter is a match because it can bind to a temporarystd::string
object created from the string literal. Therefore, the temporarystd::string
object is created using thestd::string
constructor that takes aconst char*
argument.Next, the temporary
std::string
object is passed to thesetName()
function by reference, which triggers the copy constructor ofstd::string
to create a copy of the temporary object.Finally, the temporary
std::string
object is moved to thename
data member using the move assignment operator, which results in the temporary object being destroyed.So, in Option 2, two temporary
std::string
objects are created, one for holding the string data, and another one for binding to thestd::string&&
parameter of the overloadedsetName()
function.
The author is clear about option 1:
[..] the string literal "Adela Novak" would be passed to setName, where it would be conveyed to the assignment operator for the std::string inside w. w’s name data member would thus be assigned directly from the string literal; no temporary std::string objects would arise.
A string literal is not an std::string
, it's a char array (const char[]
). So the universal reference allows you to pass an object of any type without any conversion or temporary objects.
As for option 2, a temporary object may or may not be created, depends on what you pass in setName
. If you pass an std::string
, then no temporaries will be created. If you pass a string literal, you need a temporary std::string
to be created first and then pass a reference to it.
void foo(const std::string& s) { ... }
void foo(std::string&& s) { ... }
int main() {
std::string str(10, 'a'); // "aaaaaaaaaa"
foo(str); // No temporary, passing a reference to 'str'
foo("Hello"); // Creating a temporary std::string and passing a reference to the temporary
return 0;
}