From this link, it says that
Objects, references, functions including function template specializations, and expressions have a property called type
<
Does
rf_int
have a reference type or not?
The entity (or variable) with the name rf_int
has type int&&
(a reference type) because of the way it is declared, but the expression rf_int
has type int
(a non-reference type) per [expr]/5:
If an expression initially has the type “reference to
T
” ([dcl.ref], [dcl.init.ref]), the type is adjusted toT
prior to any further analysis. The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression. [ Note: Before the lifetime of the reference has started or after it has ended, the behavior is undefined (see [basic.life]). — end note ]
Do we have to provide context when talking about the type of a name, be it a variable or an expression?
Yes, we do. rf_int
can be said to have different types depending on whether it refers to the entity or the expression.
More specifically, when a variable name is used in function call:
SomeFunc(rf_int);
Is
rf_int
now considered an expression (thus it is an lvalue with typeint
), or a variable (thus it is an lvalue with type rvalue reference toint
)?
It is considered an expression, which is an lvalue of type int
. (Note that value category is a property of expressions. It is not correct to say a variable is an lvalue.)
Based on this each function call is an expression. Each argument passed to the function is also an expression.
Therefore, when you make a call to SumFunc(rf_int);
you create an expression from the single rf_int
variable.
ISO/IEC 14882 (c++14 standard) states that "The expression designates the object or function denoted by the reference, and the expression is an lvalue or an xvalue, depending on the expression."
Thus, the rf_int
expression will be of type int.
This comment (you mention it before) is about type conversion error.
To illustrate my explanation, I have prepared a more complex, but (hopefully) more understandable example. This example is about why we need rvalue refernce type and how to use it correctly.
class Obj {
int* pvalue;
public:
Obj(int m) {
pvalue = new int[100];
for(int i = 0; i < 100; i++)
pvalue[i] = m + i;
}
Obj(Obj& o) { // copy constructor
pvalue = new int[100];
for(int i = 0; i < 100; i++)
pvalue[i] = o.pvalue[i];
}
Obj(Obj&& o) { // move constructor
for(int i = 0; i < 100; i++)
pvalue = o.pvalue;
o.pvalue = nullptr;
}
// ...
};
Example 1
Obj obj1(3);
Obj obj2(obj1); // copy constructor
Obj obj3(std::move(obj1)); // move constructor
Obj obj4(Obj(3)); // move constructor
Line #1 - obj1 created.
Line #2 - obj2 created as a copy of obj1
Line #3 - obj3 created by moving value of obj1 to the obj3; obj1 loose it's value
Line #4 - temporary object created by Obj(3); obj4 created by moving value of temporary object to obj4; temporary object loose it's value
I think we have no real need of int&&
but Obj&&
can be very useful. Especially in the line #4.
Example 2 with my (simplified) interpretation of compiler errors
void SomeFunc0(Obj arg) {};
void SomeFunc1(Obj& arg) {};
void SomeFunc2(Obj&& arg) {};
int main()
{
Obj obj1(3); // object
Obj& obj2 = obj1; // reference to the object
Obj&& obj3 = Obj(3); // reference to the temporary object with extened lifetime
SomeFunc0(obj1); // ok - new object created from Obj
SomeFunc0(obj2); // ok - new object created from Obj&
SomeFunc0(obj3); // ok - new object created from Obj&&
SomeFunc0(Obj(3)); // ok - new object created from temporary object
SomeFunc0(std::move(obj1)); // ok - new object created from temporary object
SomeFunc0(std::move(obj2)); // ok - new object created from temporary object
SomeFunc0(std::move(obj3)); // ok - new object created from temporary object
SomeFunc1(obj1); // ok - reference to obj1 passed
SomeFunc1(obj2); // ok - reference to obj1 passed
SomeFunc1(obj3); // ok - reference to temp. obj. passed
SomeFunc1(Obj(3)); // error - lifetime of the temp. obj. too short
SomeFunc1(std::move(obj1)); // error - lifetime of the temp. obj. too short
SomeFunc1(std::move(obj2)); // error - lifetime of the temp. obj. too short
SomeFunc1(std::move(obj3)); // error - lifetime of the temp. obj. too short
SomeFunc2(obj1); // error - temporary object required
SomeFunc2(obj2); // error - temporary object required
SomeFunc2(obj3); // error - lifetime of the temp. obj. too long
SomeFunc2(Obj(3)); // ok
SomeFunc2(std::move(obj1)); // ok
SomeFunc2(std::move(obj2)); // ok
SomeFunc2(std::move(obj3)); // ok
return 0;
}
It confused me first too but let me clear out the ambiguity in a simple way.
EXPRESSION is something that can be evaluated and must evaluate to a non-reference type right ?
( Yes, of course duh !! )
Now we also know a variable name is an l-value EXPRESSION.
( Dude get to the point already and stop making the expression word bold )
Okay now here is the catch, when we say a variable we mean a place in memory. Now will we call a place in memory an expression ? No, definitely not that is just completely absurd.
Expression is a generic term that we identify by defining some rules and anything that fall under those rules is an expression. It is necessary to define it this way to make sense of the code during compiler construction. One of that rule is that anything that evaluates to a value is an expression. Since from coding perspective using a variable name means that you wish the actual value is used when the code is compiled, so we call that variable name an expression.
So when they say a variable is an expression they don't mean the variable as in place in memory but the variable NAME from coding perspective. But using the term "variable name" to differentiate from the actual variable (place in memory) is just absurd. So saying "variable is an expression" is just fine as long as you think it from coding perspective.
Now to answer this first :
More specifically, when a variable name is used in function call:
SomeFunc(rf_int);
Is
rf_int
now considered an expression (thus it is an lvalue with typeint
), or a variable (thus it is an lvalue with type rvalue reference toint
)?
A single variable is also an expression. So this question becomes invalid.
Now to answer this :
Based on the above two statement, rf_int can be treated as an expression and expression has non-reference type.
Now I am really confused. Does rf_int have a reference type or not?
What if I say rf_int
is an r-value reference and rf_int
is also an l-value EXPRESSION.
( Oh brother, this guy and his obsession with expression )
It is true because if you do the following it will work.
int &&rf_int = 10; // rf_int is an r-value reference
int &x = rf_int; // x is an l-value reference and l-value reference can be initialized with l-value expression
cout << x; //Output will be 10
Now is rf_int
an expression or a r-value reference, what will it be ? The answer is both. It depends on from which perspective you are thinking.
In other words what I'm trying to say is that if we think rf_int
as a variable (some place in memory) then surely it has the type of r-value reference but since rf_int
is also a variable name and from coding perspective it is an expression and more precisely an l-value expression and whenever you use this variable for the sake of evaluation you will get the value 10
which is an int
so we are bound to say that rf_int
type as an expression is an int
which is a non-reference type.
If you think from compiler perceptive for a moment what line of code evaluates to a reference ? None right ?. You can try searching if you find any do let me know as well. But the point here is that the type of expression doesn't mean type of variable. It means the type of value that you get after evaluating the expression.
Hope I have clarified your question. If I missed something do let me know.