I'm interested in why rvalue references are called exactly "rvalue references". How does it relate to rvalue, prvalue etc concepts. The section N3797::5/5 says:
If an expression initially has the type “reference to T” (8.3.2,
8.5.3), the type is adjusted to T 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.
That's an expression in which rvalue reference involves is adjusted to an lvalue or an xvalue, but not to rvalue or prvalue.
Related
The following quotes are needed in the question:
[dcl.init.ref]/5:
5- A reference to type “cv1 T1” is initialized by an expression of
type “cv2 T2” as follows:
(5.1) [..]
(5.2) [..]
(5.3) Otherwise, if the initializer expression
(5.3.1) is an rvalue (but not a bit-field) or function lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or
(5.3.2) [..]
then the value of the initializer expression in the first case and the result of the conversion in the second case is called the converted initializer. If the converted initializer is a prvalue, its type T4 is adjusted to type “cv1 T4” ([conv.qual]) and the temporary materialization conversion ([conv.rval]) is applied. In any case, the reference is bound to the resulting glvalue (or to an appropriate base class subobject).
(emphasis mine)
[expr.type]/2:
If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.
Consider the following example:
const int&& r1 = 0;
Taking cv1 T1 as const int, and cv2 T2 as int
It's clear that bullet [dcl.init.ref]/(5.3.1) is applied here. The initializer expression is an rvalue (prvalue); and cv1 T1 (const int) is reference-compatible with cv2 T2 (int). And since that the converted initializer is prvalue, its type T4 (int) is adjusted to cv1 T4 (const int). Then, temporary materialization is applied.
But, per [expr.type]/2, before applying temporary materialization conversion, cv1 T4 (const int) becomes int again. Then, by applying temporary materialization, we've got an xvalue denoting an object of type int. Then the reference is bound to the resulting glvalue.
Here's my first question. The reference r1 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r1, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding? Is any missing wording? Am I misunderstood/missed something?
Consider another last example:
const int&& r2 = static_cast<int&&>(0);
The same wording as above applies: The initializer expression is an rvalue (xvalue) and cv1 T1 (const int) is reference-compatible with cv2 T2 (int). And since that the converted initializer is an xvalue not prvalue, [conv.qual] or even [conv.rval] is not applied (i.e, the condition "If the converted initializer is a prvalue, ..") isn't satisfied.
I know that [conv.rval] isn't needed here since the initializer expression is already an xvalue, but [conv.qual] is required.
And that's my last question. The reference r2 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r2, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding? Is any missing wording? Am I misunderstood/missed something?
Here's my first question. The reference r1 is reference to const int and the resulting glvalue is an xvalue denoting an object of type int. So how r1, which is of type const int&&, is now binding to an xvalue of type int? Is this valid binding?
Yes, that's what happens. I fail to see the issue here.
I know that [conv.rval] isn't needed here since the initializer expression is already an xvalue, but [conv.qual] is required.
No it isn't. Again, I fail to see the issue.
There is, in fact, no rule that says that a reference of type T&&, can only refer to an object whose type is exactly T. A const int&& can refer to an int object. The concept of reference-compatibility was invented in order to describe what types of objects a reference can refer to.
Look at this expression:
T t;
T& ref = t;
The ref expression is an lvalue or an rvalue? I believe it's an rvalue because ref does not "designates a function or an object":
"An lvalue (so called, historically, because lvalues could appear on
the left-hand side of an assignment expression) designates a function
or an object."
[open-std draft n3092]
According to cppreference, a reference is not a object.
"The following entities are not objects: value, reference [...]"
I'm in doubt, because ref is in the left side of =.
I'm in doubt, because ref is in the left side of =.
ref is a name and therefore an lvalue.
I believe it's an rvalue because ref does not "designates a function or an object"
It designates the same object as t does.
A reference does designate a function or object! It is literally written like that in the standard! See in [expr.type]/1:
If an expression initially has the type “reference to T” ([dcl.ref], [dcl.init.ref]), the type is adjusted to T prior to any further analysis.
The expression designates the object or function denoted by the reference,[...]
I know every expression in c++ has a category (prvalue, xvalue, lvalue..) and a type which according to the standard draft, is never of reference type (may be cv qualified if not a prvalue)
5 If an expression initially has the type “reference to T” (8.3.2,
8.5.3), the type is adjusted to T 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.
6 If a prvalue initially has the type “cv T,” where T is a
cv-unqualified non-class, non-array type, the type of the expression
is adjusted to T prior to any further analysis.
Given that decltype has its own set of rules and the auto deduction has a different one as well, when does this “non-reference expression type” matter?
It matters in unevaluated expressions:
typeid:
typeid(std::cout << 0) == typeid(std::ostream);
// true
noexcept:
template<class T> void f() noexcept(noexcept(T{}+T{}))
sizeof (even though sizeof has a specific rule, non contradicting with the rule for types of full expressions):
sizeof(std::cout << 0);
// the expression returns an std::ostream&, but its type is std::ostream
etc.
The snippet below compiles
#include <iostream>
int& f() { static int i = 100; std::cout << i << '\n'; return i; }
int main()
{
int& r = f();
r = 101;
f();
}
and print the values (live example)
100
101
Now, reading §8.5.3/5 in N4140, I can see that it compiles because of bullet point (5.1.1), that is, the reference is an lvalue reference, the initializer expression is an lvalue and int is reference-compatible with int (or with int& - I don't know for sure which one I should use here).
Bullet points (5.1) and (5.1.1):
— If the reference is an lvalue reference and the initializer expression
— is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with
“cv2 T2,” or ...
Now suppose I change the left value reference in the declaration int& r = f(); by a right value reference, i.e., int&& r = f();. I know the code won't compile, as an rvalue reference doesn't bind to an lvalue. But what I'm curious is, how to reach this conclusion using the Standard?
I'll explain what are my difficulties:
Clearly int&& r = f(); is covered by bullet point (5.2), because the reference is an rvalue reference.
Bullet point (5.2):
— Otherwise, the reference shall be an lvalue reference to a
non-volatile const type (i.e., cv1 shall be const), or the reference
shall be an rvalue reference.
In principle, I would say that (5.2.1.1) supports this initialization as the initializer is a function lvalue and int is reference compatible with int (or with int&).
Bullet points (5.2.1) and (5.2.1.1):
— If the initializer expression
— is an xvalue (but not a bit-field), class prvalue, array prvalue or function
lvalue and “cv1 T1” is reference-compatible with “cv2 T2”, or ...
Edit
I've included the bullet points verbatim from N4140 (C++14), which are equivalent to similar bullet points in N3337 (C++11).
the initializer expression is an lvalue and int is reference-compatible with int (or with int& - I don't know for sure which one I should use here).
Reference-compatibility is a relation applied to the type referred to, not the reference type. For example, [dcl.init.ref]/5 talks about intializing "a reference to type cv1 T1 by an expression of type cv2 T2", and later compares e.g. "where T1 is not reference-related to T2".
The type of the expression f() is just int, despite the fact that the return type of f is int&. Expressions simply do not have reference type when we observe them(*); the reference is stripped and used to determine the value category (see [expr]/5). For int& f(), the expression f() is an lvalue; for int g(), the expression g() is an rvalue.
(*)To be perfectly precise, expressions can have reference type in the Standard, but only as the "initial" resulting type. The reference is dropped "prior to any further analysis", which implies that this referencess is simply not observable through the type.
Now suppose I change the left value reference in the declaration int& r = f(); by a right value reference, i.e., int&& r = f();. I know the code won't compile, as an rvalue reference doesn't bind to an lvalue. But what I'm curious is, how to reach this conclusion using the Standard?
The confusion, as it seems from the discussion in the comments, seems to be that f() is not a function lvalue. Value categories such as "lvalue" and "rvalue" are properties of expressions. The term "function lvalue" must therefore refer to an expression, namely an expression of function type with the value category "lvalue".
But the expression f() is a function call expression. Grammatically, it's a postfix-expression, the postfix being the function argument list. As per [expr.call]/10:
A function call is an lvalue if the result type is an lvalue reference type or an rvalue reference to function type, an xvalue if the result type is an rvalue reference to object type, and a prvalue otherwise.
And [expr.call]/3
If the postfix-expression designates a destructor [...]; otherwise, the type of the function call expression is the return type of the statically chosen function [...]
That is, the (observed see above) type of the expression f() is int, and the value category is "lvalue". Note that the (observed) type is not int&.
A function lvalue is for example an id-expression like f, the result of indirecting a function pointer, or an expression yielding any kind of reference to function:
using ft = void();
void f();
ft& l();
ft&& r();
ft* p();
// function lvalue expressions:
f
l()
r()
*p()
[expr.prim.general]/8 specifies that those identifiers like f are, as id-expressions, lvalues:
An identifier is an id-expression provided it has been suitably declared. [...] The type of the expression is the type of the identifier. The result is the entity denoted by the identifier. The result is an lvalue if the entity is a function, variable, or data member and a prvalue otherwise.
Back to the example int&& r = f();. Using some post-N4296 draft.
[dcl.init.ref]
5 A reference to type “cv1 T1” is initialized by an expression of type
“cv2 T2” as follows:
(5.1) If the reference is an lvalue reference and the initializer expression
The reference is an rvalue reference. 5.1 does not apply.
(5.2) Otherwise, the reference shall be an lvalue reference to a
non-volatile const type (i.e., cv1 shall be const), or the reference
shall be an rvalue reference. [example omitted]
This applies, the reference is an rvalue-reference.
(5.2.1) If the initializer expression
(5.2.1.1) is an xvalue (but not a bit-field), class prvalue, array prvalue or function lvalue and [...], or
(5.2.1.2) has a class type (i.e., T2 is a class type) [...]
The initializer is an lvalue of type int. 5.2.1 does not apply.
(5.2.2) Otherwise:
(5.2.2.1) If T1 or T2 is a class type [...]
(5.2.2.2) Otherwise, a temporary of type “cv1 T1” is created and copy-initialized (dcl.init) from the initializer expression. The reference is then bound to the temporary.
Finally, 5.2.2.2 applies. However:
If T1 is reference-related to T2:
(5.2.2.3) cv1 shall be the same cv-qualification as, or greater cv-qualification than, cv2; and
(5.2.2.4) if the reference is an rvalue reference, the initializer expression shall not be an lvalue.
T1 and T2 are int (the reference of the return type of f() is removed and used only to determine the value category), so they're reference-related. cv1 and cv2 are both empty. The reference is an rvalue reference, and f() is an lvalue, hence 5.2.2.4 renders the program ill-formed.
The reason why the term "function lvalue" appears in 5.2.1.1 might be related to the problem of "function rvalues" (see, for example, N3010 - Rvalue References as "Funny" Lvalues). There were no function rvalues in C++03, and it seems the committee didn't want to introduce them in C++11. Without rvalue references, I think it's impossible to get a function rvalue. For example, you may not cast to a function type, and you may not return function types from a function.
Probably for consistency, function lvalues can be bound to rvalue references to function types via a cast:
template<typename T>
void move_and_do(T& t)
{
T&& r = static_cast<T&&>(t); // as if moved
}
int i = 42;
move_and_do(i);
move_and_do(f);
But for T being a function type like void(), the value category of static_cast<T&&>(t) is lvalue (there are no rvalues of function type). Hence, rvalue references to function types can bind to function lvalues.
int x = 8;
int y = x ;
Here how a lvalue can be act as rvalue ? I know this is a silly question , but i just want to make my concepts clear on rvalue and lvalue .
Through an lvalue to rvalue conversion.
You can use an lvalue almost anywhere where an rvalue is required and an implicit lvalue to rvalue conversion will occur automatically.
Informally this conversion is "evaluating" or "taking the value of" the object that the lvalue refers to. This isn't strictly true in all cases; in unevaluated contexts, such as the operand of sizeof, the value of the object won't be used.
ISO/IEC 14882:2011 4.1 [conv.lval]:
A glvalue (3.10) of a non-function, non-array type T can be converted to a prvalue. If T is an incomplete type, a program that necessitates this conversion is ill-formed. If the object to which the glvalue refers is not an object of type T and is not an object of a type derived from T, or if the object is uninitialized, a program that necessitates this conversion has undefined behavior. If T is a non-class type, the type of the prvalue is the cv-unqualified version of T. Otherwise, the type of the prvalue is T.
An lvalue is any value that can appear on the left hand side of an assignment. Something that can be assigned to, like the variable x in this case. Rvalues are things that have a value, like 8 or getting the value of x. You can think of lvalues as a subset of rvalues, that is all lvalues can have their value queried and used as an rvalue.