1

Now that GCC 4.8.1 and Clang 2.9 and higher support them, reference qualifiers (also known as "rvalue references for *this") have become more widely available. They allow classes to behave even more like built-in types by, e.g., disallowing assignment to rvalues (which can otherwise cause an unwanted cast of an rvalue to an lvalue):

class A
{
    // ...

public:
    A& operator=(A const& o) &
    {
        // ...
        return *this;
    }
};

In general, it is sensible to call a const member function of an rvalue, so an lvalue reference qualifier would be out of place (unless the rvalue qualifier can be used for an optimization such as moving a member out of a class instead of returning a copy).

On the flip side, mutating operators such as the pre decrement/increment operators should be lvalue-qualified, as they usually return an lvalue-reference to the object. Hence also the question: Are there any reasons to ever allow mutating/non-const methods (including operators) to be called on rvalue references aside from conceptually const methods which are only not marked const because const-correctness (including proper application of mutable when using an internal cache, which may include ensuring certain thread-saftey guarantees now) was neglected in the code base?

To clarify, I am not suggesting to forbid mutating methods on rvalues on the language level (at the very least this could break legacy code) but I believe that defaulting (as an idiom / coding style) to only allowing lvalues for mutating methods will generally lead to cleaner, safer APIs. However I am interested in examples where not doing so leads to cleaner, less astonishing APIs.

Community
  • 1
  • 1
Joe
  • 6,497
  • 4
  • 29
  • 55

4 Answers4

3

A mutator that operates on an R-value can be useful if the R-value is used to accomplish some task, but in the interim it maintains some state. For example:

struct StringFormatter {
     StringFormatter &addString(string const &) &;
     StringFormatter &&addString(string const &) &&;
     StringFormatter &addNumber(int) &;
     StringFormatter &&addNumber(int) &&;
     string finish() &;
     string finish() &&;
};
int main() {
    string message = StringFormatter()
            .addString("The answer is: ")
            .addNumber(42)
            .finish();
    cout << message << endl;
}

By allowing either an L-value or an R-value, one can construct an object, pass it through some mutators, and use the result of the expression to accomplish some task without having to store it in an L-value, even if the mutators are member functions.

Also note that not all mutating operators return a reference to the self. User-defined mutators can implement any signature they need or want. A mutator may consume the state of the object to return something more useful, and by acting on an R-value, the fact that the object is consumed isn't a problem since the state would have otherwise been discarded. In fact, a member function that consumes the state of the object to produce something else useful will have to be marked as such, making it easier to see when l-values are consumed. For example:

MagicBuilder mbuilder("foo", "bar");

// Shouldn't compile (because it silently consumes mbuilder's state):
// MagicThing thing = mbuilder.construct();

// Good (the consumption of mbuilder is explicit):
MagicThing thing = move(mbuilder).construct();
Adam H. Peterson
  • 4,511
  • 20
  • 28
  • The first example looks very suspicious to me. What invariants does StringFormatter maintain? Wouldn't a set of free functions/operators be far more appropriate? The second example looks like two-phase construction (evil) to me. Both examples smell of Java (both due to to the choice of names, as well as the use of objects for things that have no real reason to be objects) – Joe Jun 06 '13 at 22:58
  • @Joe, They're both valid use cases and it makes sense to me that C++11 would support them. At my work, we've used builders like the second example to build complex objects in a similar manner (for example, where you use the builder to accumulate values and then sort them to construct the final object), although we usually do that by having the builder consumed by the constructor rather than having the builder consume itself to return an object. (And if you suggested I'm a fan of Java to my coworkers, they'd probably laugh out loud.) (`StringFormatter` comment below:) – Adam H. Peterson Jun 06 '13 at 23:08
  • 1
    @Joe, the `StringFormatter` example would make more sense if you replace `addString` and `addNumber` with `operator<<`, and think of a `stringstream`. As `stringstream`s are currently standardized, you can't do `auto foo = (ostringstream() << "The answer is: " << 42).str()`, but it would be very convenient. Perhaps if we'd had r-value references when they were created, this usage would have been supported. – Adam H. Peterson Jun 06 '13 at 23:10
  • @Joe, there's also a big difference between the second example and two-phase construction. With two-phase construction, an object's constructor does a partial-setup and the user has to call some member function to finish the job (so for a while you have a half-constructed object). With the builder idiom, there is no partially constructed object because it isn't created until the builder is ready to construct it fully formed. (I don't care for two-phase construction; it feels broken to me. But I can't think of any argument against two-phase construction that applies to this builder idiom.) – Adam H. Peterson Jun 06 '13 at 23:32
  • I'm not saying they should be forbidden (it could break legacy code). Just that if you need them, you are probably doing it wrong. In the case of the formatter for example a variadic function would be far more appropriate (no 'finalizer' required and it could in fact precompute the length to avoid multiple reallocations). – Joe Jun 07 '13 at 00:15
  • In the case of the parameter accumulator (which does avoid most two phase construction issues, but still seems to indicate that whatever needs to accumulate parameters is probably doing way too many things at once) why not make `MagicThing` take an rvalue reference to a `MagicBuilder` in it's constructor. Far clearer IMO and [much less astonishing](http://en.m.wikipedia.org/wiki/Principle_of_least_astonishment). – Joe Jun 07 '13 at 00:16
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31363/discussion-between-adam-h-peterson-and-joe) – Adam H. Peterson Jun 07 '13 at 00:20
  • 1
    The technique in the first code block has also been used by Stroustrup in D&E as an argument why not to introduce named/keyword arguments as an additional language feature. – dyp Jun 29 '14 at 21:35
0

I think it comes about in cases where the only way to retrieve some value is by mutating another value. For instance, iterators don't provide a "+1" or a "next" method. So suppose I'm constructing a wrapper for stl list iterators (perhaps to create an iterator for my own list-backed data-structure):

class my_iter{
private:
    std::list::iterator<T> iter;
    void assign_to_next(std::list::iterator<T>&& rhs) {
        iter = std::move(++rhs);
    }
};

Here, the assign_to_next method takes an iterator and assigns this one to have the next position after that one. It's not too hard to imagine situations where this might be useful, but more importantly there is nothing surprising about this implementation. True, we could also say iter = std::move(rhs); ++iter; or ++(iter = std::move(rhs));, but I don't see any arguments for why those would be any cleaner or faster. I think this implementation is the most natural to me.

dspyz
  • 5,280
  • 2
  • 25
  • 63
  • I'm not sure what you're trying to accomplish. E.g. there's [`std::next`](http://en.cppreference.com/w/cpp/iterator/next), which works by copying an iterator and incrementing the copy. `it1 = std::next(it0);` – dyp Dec 22 '13 at 19:44
  • Oh, I didn't know about std::next. Then I guess my example's wrong. I can't think of anything. – dspyz Dec 22 '13 at 20:57
0

FWIW HIC++ agrees with you as far as assignment operators:

http://www.codingstandard.com/rule/12-5-7-declare-assignment-operators-with-the-ref-qualifier/


Should a non-const method ever apply to rvalues?

This question puzzles me. A more sensible question to me would be:

Should a const method ever apply exclusively to rvalues?

To which I believe the answer is no. I can't imagine a situation in which you would want to overload on const rvalue *this, just as I can't imagine a situation in which you would want to overload on const rvalue arguments.

You overload on rvalues because it's possible to handle them more efficiently when you know that you can steal their guts, but you can't steal the guts of a const object.

There are four possible ways to overload on *this:

struct foo {
    void bar() &;
    void bar() &&;
    void bar() const &;
    void bar() const &&;
};

The constness of the latter two overloads means that neither one can mutate *this, so there can be no difference between what the const & overload is allowed to do to *this and what the const && overload is allowed to do to *this. In the absence of the const && overload, the const & will bind to both lvalues and rvalues anyway.

Given that overloading on const && is useless and only really provided for completeness (prove me wrong!) we are left with only one remaining use case for ref-qualifiers: overloading on non-const rvalue *this. One can define a function body for a && overload, or one can = delete it (this happens implicitly if only a & overload is provided). I can imagine plenty of cases in which defining a && function body might be useful.

A proxy object which implements pointer semantics by overloading operator-> and unary operator*, such as boost::detail::operator_arrow_dispatch, might find it useful to use ref-qualifiers on its operator*:

template <typename T>
struct proxy {
    proxy(T obj) : m_obj(std::move(obj)) {}
    T const* operator->() const { return &m_obj; }
    T operator*() const& { return m_obj; }
    T operator*() && { return std::move(m_obj); }
private:
    T m_obj;
};

If *this is a rvalue then operator* can return by move instead of by copy.

Oktalist
  • 14,336
  • 3
  • 43
  • 63
  • Thanks for the HIC++ reference, but "This question puzzles me. A more sensible question to me would be: Should a const method ever apply exclusively to rvalues?" makes absolutely no sense to me, and doesn't seem related to the question. – Joe Jun 29 '14 at 21:27
  • Although `const &&` is of little use, your example overloads `const` methods for (non-`const`) rvalues as an optimization. In this case the method is the same in intent as the `const` version, except that it is optimized under the assumption that the object may have any state afterwards since it is temporary. – Joe Jun 29 '14 at 21:33
-1

I can imagine functions that move from the actual object to a parameter.

Balog Pal
  • 16,195
  • 2
  • 23
  • 37