Since I'm a beginner in c++, some questions don't quite understand. This question came across by accident while I was reading C++ primer 5th.
I have a Cat
class with 3 different constructors(named by Constructor 1, Constructor 2, Constructor 3):
class Cat {
friend class Cats;
private:
std::string name;
public:
Cat() = default;
// Constructor 1
Cat(std::string n): name(n) {
std::printf("Call: Cat(std::string n)\n");
}
// Constructor 2
Cat(std::string& n): name(n) {
std::printf("Call: Cat(std::string& n)\n");
}
// Constructor 3
Cat(const std::string& n): name(n) {
std::printf("Call: Cat(const std::string &n)\n");
}
};
And I want to instantiate the class in two different ways:
class C7T17_Main {
public:
void run() {
// Instantiation 1
Cat cat(std::string("Cathy"));
// Instantiation 2
std::string s("Mike");
Cat cat2(s);
}
};
Then the problem comes:
For Constructor 1
:
Instantiation 1
and Instantiation 2
both work wellFor Constructor 2
:
Instantiation 1
raises an error. The compiler complains that 'Candidate constructor not viable: expects an lvalue for 1st argument'Instantiation 2
works normally.For Constructor 3
:
Instantiation 1
and Instantiation 2
both work wellMy guess is:
Instantiation 1
does not actually create a variable, but a temporary value for initializing cat, so it is not suitable as a reference parameter.
For constructor 3
, the const reference parameter represents a constant that can accept a temporary value for initialization
Looking forward to your help. :)
It's pretty simple:
Instantiation 1 does not actually create a variable, but a temporary value for initializing cat, so it is not suitable as a reference parameter.
Yes, it creates a prvalue, and it can be passed by reference like this:
// ctor 4
Cat( std::string&& n ) // see the double ampersand
ctor 4 only accepts temporaries (rvalues) by reference.
For constructor 3, the const reference parameter represents a constant that can accept a temporary value for initialization
Yes, it can bind to all the values as I mentioned previously.
For Constructor 2: Instantiation 1 raises an error. The compiler complains that 'Candidate constructor not viable: expects an lvalue for 1st argument'
This is the expected behavior.
Important Note:
Cat(std::string&& n): name(n) { // here n is of type rvalue refernece to string
std::printf("Call: Cat(std::string& n)\n");
}
Function parameters that are of type rvalue reference are lvalues themselves. So in the above code, n
is a variable and it's an lvalue. Thus it has an identity and can not be moved from (you need to use std::move
in order to make it movable).
You can even pass an lvalue to a function that only accepts rvalues like this:
Cat( std::string&& n );
std::string s("Mike");
Cat cat2( std::move(s) );
std::move
performs a simple cast. It casts an lvalue to an xvalue so it becomes usable by a function that only accepts an rvalue.
In C++11, expressions that:
- have identity and cannot be moved from are called lvalue expressions;
- have identity and can be moved from are called xvalue expressions;
- do not have identity and can be moved from are called prvalue ("pure rvalue") expressions;
- do not have identity and cannot be moved from are not used[6].
The expressions that have identity are called "glvalue expressions" (glvalue stands for "generalized lvalue"). Both lvalues and xvalues are glvalue expressions.
The expressions that can be moved from are called "rvalue expressions". Both prvalues and xvalues are rvalue expressions.
Read more about this topic at value categories.