can you expand a bit on how rvalue references differ from lvalue references please? |
The distinction is straightforward, ignoring esoteric stuff like we just discussed:
- lvalue references bind to lvalue expressions;
- rvalue references bind to rvalue expressions.
But this isn't helpful without an understanding of lvalue and rvalue expressions.
Every expression has a
value category and a
type. Some expressions are categorized as
rvalue expressions, while others are categorized as
lvalue expressions.
Note that value category is a property of
expressions (e.g.,
f(2)+3 or
x), and
not a property of objects nor even of values. There is no relationship between "rvalues" (i.e., rvalue expressions) and unnamed objects or temporary objects.
As a
rough approximation, an expression is an lvalue if you can take its address. For instance, we can use this rule to determine that the expression
1.2+3.4 is an rvalue, because
&(1.2+3.4) does not compile. Similarly we can determine that the expression
std::cout is an lvalue because
&std::cout compiles just fine. However, this is only an approximation. If there is any doubt you'll need to look at this fairly intimidating reference page (don't read it now):
https://en.cppreference.com/w/cpp/language/value_category
If an rvalue expression (
2) is used to initialize an rvalue reference
int&& rrx = 2;
Then an object of type
int is (created, or "materialized", then) used to initialize
rrx. That object's lifetime is extended to the lifetime of the reference. The lifetime is only extended if the object is temporary.
Since
rrx is mutable, we can modify the referenced object through
rrx:
rrx = 4;
Because of a special case, lvalue references to
const can bind to rvalues too, with similar behavior:
int const& crx = 2;
This old rule allows us to pass rvalues as function arguments:
1 2 3 4
|
std::string g() { return "foo"; }
void f(std::string const& s);
f(g()); // ok, despite that g() is an rvalue expression
|
Although clearly the referenced object can't be modified through
crx - it's
const:
crx = 4; // error
However, lvalue references to non-const cannot (usually) be initialized with rvalue expressions. This is demonstrated above.
The expression
rrx is an lvalue. If we take its address we get the address of the object bound to the reference. This is exactly the same situation as taking the address of
crx. The point is that rvalue references are not automatically rvalues. References are aliases for objects, and objects do not have value category. This is fundamental, so make sure you understand why it's true.
In the ctor, is Func&& always an rvalue reference or is it a forwarding reference |
It's an rvalue reference.
Forwarding references appear in two cases:
1.)
auto&& x = expression;
2.)
template <typename T> void f(T&& x) {}; f(expression);
Class template argument deduction, e.g.:
3.)
template <typename T> struct A { A(T&& x) {} } a(expression);
Does not produce forwarding references - so
Func&& is always an rvalue reference.