What are the most typical use cases of \"rvalue references for *this\" which the standard also calls reference qualifiers for member functions?
By the way, there is
When called, each member function has an implicit object parameter that *this
references.
So (a) these normal function overloads:
void f(const T&);
void f(T&&);
when called like f(x)
; and (b) these member function overloads:
struct C
{
void f() const &;
void f() &&;
};
when called like x.f()
- both (a) and (b) dispatch with similar viability and ranking.
So the use cases are essentially the same. They are to support move semantic optimization. In the rvalue member function you can essentially pillage the objects resources because you know that it is an expiring object (is about to be deleted):
int main()
{
C c;
c.f(); // lvalue, so calls lvalue-reference member f
C().f(); // temporary is prvalue, so called rvalue-reference member f
move(c).f(); // move changes c to xvalue, so again calls rvalue-reference member f
}
So for example:
struct C
{
C operator+(const C& that) const &
{
C c(*this); // take a copy of this
c += that;
return c;
}
C operator+(const C& that) &&
{
(*this) += that;
return move(*this); // moving this is ok here
}
}
In my compiler framework (to be released Sometime Soon™), you pass items of information such as tokens into a compiler object, then call finalize
to indicate the end of stream.
It would be bad to destroy an object without calling finalize
, because it wouldn't flush out all its output. Yet finalize
can't be done by the destructor, because it can throw an exception, and likewise it's wrong to ask finalize
for more output if the parser is already aborting.
In the case when all the input is already encapsulated by another object, it's nice to pass input to an rvalue compiler object.
pile< lexer, parser >( output_handler ).pass( open_file( "source.q" ) );
Without special support, this must be incorrect because finalize
isn't getting called. The interface shouldn't let the user do such a thing at all.
The first thing to do is rule out the case where finalize
never gets called. The above example is disallowed if the prototype is adjusted with an lvalue ref-qualifier like this:
void pass( input_file f ) & {
process_the_file();
}
This makes room to add another overload which properly finalizes the object. It is rvalue ref-qualified so it is selected only if called on an object which is expiring.
void pass( input_file f ) && {
pass( std::move( f ) ); // dispatch to lvalue case
finalize();
}
Now the user almost never needs to worry about remembering to call finalize
, since most compiler objects are ultimately instantiated as temporaries.
Note, this sort of thing isn't particular to ref-qualified members. Any function can have separate overloads for t &
and t &&
. The way pass
is actually presently implemented uses perfect forwarding and then backtracks to determine the correct semantics:
template< typename compiler, typename arg >
void pass( compiler && c, arg a ) {
c.take_input( a );
if ( ! std::is_reference< compiler >::value ) {
c.finalize();
}
}
There are many ways to approach overloading. Actually, unqualified member functions are unusual in not caring about the category (lvalue or rvalue) of the object they are called on, and not passing that information into the function. Any function parameter besides the implicit this
must say something about the category of its argument.
Some operations can be more efficient when called on rvalues so overloading on the value category of *this
allows the most efficient implementation to be used automatically e.g.
struct Buffer
{
std::string m_data;
public:
std::string str() const& { return m_data; } // copies data
std::string str()&& { return std::move(m_data); } // moves data
};
(This optimisation could be done for std::ostringstream
, but hasn't been formally proposed AFAIK.)
Some operations don't make sense to call on rvalues, so overloading on *this
allows the rvalue form to be deleted:
struct Foo
{
void mutate()&;
void mutate()&& = delete;
};
I haven't actually needed to use this feature yet, but maybe I'll find more uses for it now that the two compilers I care about support it.