In Effective Modern C++ item 12, there is a sample code about the C++11 functions' reference qualifiers:
class Widget {
public:
using DataType = std::vector<double>;
…
DataType& data() & // for lvalue Widgets
{ return values; } // return lvalue
DataType data() && // for rvalue Widgets
{ return std::move(values); } // return rvalue
…
private:
DataType values;
};
So why does the second data()
rvalue reference overload function return a temporary object DataType
but not an rvalue reference DataType&&
?
In C++11, however, there's a new kind of reference, an "rvalue reference", that will let you bind a mutable reference to an rvalue, but not an lvalue. In other words, rvalue references are perfect for detecting if a value is temporary object or not. Rvalue references use the && syntax instead of just &, and can be const and non-const, ...
In this case, the && override would be called because the Widget object return by buildWidget does not have a name, and is this an rvalue reference. In concrete terms, as soon as the call to data () is done, the underlying Widget object (unnamed here) is going to immediately die (it's lifetime will end).
However once the const keyword was added to the C++, lvalues were split into — rvalue — The expression that refers to a disposable temporary object so they can’t be manipulated at the place they are created and are soon to be destroyed. An address can not be taken of rvalues.
Therefore your return value is a reference to an object that no longer exists. It'd be the same as if you did an l-value reference. You can only return a reference (r-value or l-value) if the object you're referring to has lifetime longer than the function. In both your examples, abc has a lifetime limited by the function -- so they're both bad.
The only reason I see would be to avoid creation of dangling reference when the object is the result of a prvalue:
Widget foo();
auto&& x = foo().data();
If foo().data()
returned an rvalue reference to values
member, x
would be a dangling reference, because the result object of foo()
is destroyed at the end of the initialization of x
(the end of the full-expression).
On the other hand, with data()&&
returning by value, x
is bound to a temporary materialization that will have the same life time as x
. So the dangling reference is avoided.
This return type for data() &&
is not idiomatic in C++. Usually, accessor functions return a reference and such use-case as the one above will probably raises the "dangling reference alarm" of any code reviewer.
This definition of data()&&
is smart but it breaks a common convention.
The &
or &&
after the method serve a special purpose: they serve as a ref value qualifier.
In a sense, all of the things you can put after a function's argument declarations (i.e. after the )
of the parameters) means "only use this method when *this
has these qualities".
Most typically usage here is const
qualifying: only calling an override in a const context:
class Widget {
void foo() {
cout << "Calling mutable foo\n";
}
void foo() const {
cout << "Calling const foo\n";
}
};
The same thing can be done with &&
or &
, to tell the compiler to select those overrides when *this
has matching qualifiers.
But why would you want to do this?
This is useful when you want to differentiate between one of the following:
*this
is an rvalue reference and will not outlive the calling code's reference.A common example that bites a lot of people is the use in for loops.
Consider the following code (building on your Widget
example):
Widget buildWidget() { return Widget(); }
int main() {
for (auto i : buildWidget().data()) {
cout << i << '\n';
}
return 0;
}
In this case, the &&
override would be called because the Widget
object return by buildWidget
does not have a name, and is this an rvalue reference.
In concrete terms, as soon as the call to data()
is done, the underlying Widget
object (unnamed here) is going to immediately die (it's lifetime will end). If this &&
override didn't exist, the lvalue reference into its data
member would point to a destructed object. This would be undefined behavior.
In general, this rvalue ref qualifying thing is not a common thing. It really only needs to be used when you run into a situation like this (often when you want to return a value by copy when *this
may be destructed after the call).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With