Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What are "rvalue references for *this" for?

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 a really good explanation about this language feature here.

like image 744
Ralph Tandetzky Avatar asked Jul 08 '13 07:07

Ralph Tandetzky


People also ask

What is the use of rvalue reference?

Rvalue references is a small technical extension to the C++ language. Rvalue references allow programmers to avoid logically unnecessary copying and to provide perfect forwarding functions. They are primarily meant to aid in the design of higer performance and more robust libraries.

Is an rvalue reference an rvalue?

An rvalue reference can only bind to an rvalue (without a static_cast being involved), but it is otherwise an lvalue of type rvalue reference. The fact it is of type rvalue reference only matters during its construction, and if you do decltype(variable_name) . It is otherwise just another lvalue of reference type.

What is rvalue reference stackoverflow?

rvalue reference and lvalue reference are categories of references. Inside a declaration, T x&& = <initializer expression> , the variable x has type T&&, and it can be bound to an expression (the ) which is an rvalue expression. Thus, T&& has been named rvalue reference type, because it refers to an rvalue expression.

What is a const rvalue reference?

You probably haven't seen any practical code that uses const rvalue references ( const T&& ). And that's not really surprising. The main purpose of rvalue references is to allow us to move objects instead of copying them. And moving the state of an object implies modification.


3 Answers

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
    }
}
like image 76
Andrew Tomazos Avatar answered Oct 13 '22 19:10

Andrew Tomazos


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.

like image 23
Jonathan Wakely Avatar answered Oct 13 '22 20:10

Jonathan Wakely


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.

like image 33
Potatoswatter Avatar answered Oct 13 '22 19:10

Potatoswatter