31

C++11 makes it possible to overload member functions based on reference qualifiers:

class Foo {
public:
  void f() &;   // for when *this is an lvalue
  void f() &&;  // for when *this is an rvalue
};

Foo obj;
obj.f();               // calls lvalue overload
std::move(obj).f();    // calls rvalue overload

I understand how this works, but what is a use case for it?

I see that N2819 proposed limiting most assignment operators in the standard library to lvalue targets (i.e., adding "&" reference qualifiers to assignment operators), but this was rejected. So that was a potential use case where the committee decided not to go with it. So, again, what is a reasonable use case?

TemplateRex
  • 69,038
  • 19
  • 164
  • 304
KnowItAllWannabe
  • 12,972
  • 8
  • 50
  • 91

4 Answers4

25

In a class that provides reference-getters, ref-qualifier overloading can activate move semantics when extracting from an rvalue. E.g.:

class some_class {
  huge_heavy_class hhc;
public:
  huge_heavy_class& get() & {
    return hhc;
  }
  huge_heavy_class const& get() const& {
    return hhc;
  }
  huge_heavy_class&& get() && {
    return std::move(hhc);
  }
};

some_class factory();
auto hhc = factory().get();

This does seem like a lot of effort to invest only to have the shorter syntax

auto hhc = factory().get();

have the same effect as

auto hhc = std::move(factory().get());

EDIT: I found the original proposal paper, it provides three motivating examples:

  1. Constraining operator = to lvalues (TemplateRex's answer)
  2. Enabling move for members (basically this answer)
  3. Constraining operator & to lvalues. I suppose this is sensible to ensure that the "pointee" is more likely to be alive when the "pointer" is eventually dereferenced:
struct S {
  T operator &() &;
};

int main() {
  S foo;
  auto p1 = &foo;  // Ok
  auto p2 = &S();  // Error
}

Can't say I've ever personally used an operator& overload.

Casey
  • 41,449
  • 7
  • 95
  • 125
  • 1
    `huge_heavy_class&& get() &&` is wrong (causes a dangling reference, shoukd be `huge_heavy_class get() &&`), and `auto hhc = std::move(factory().get());` would be redundant. – ildjarn Jan 11 '14 at 03:30
  • 1
    @ildjarn Why are you concerned about returning an rvalue reference, and not about returning an lvalue reference? One is no more likely to dangle than another. And yes, the point of the post is that it's a lot of code to write to avoid needing to write `std::move(factory().get())` - I suppose I need to make that clearer. – Casey Jan 11 '14 at 06:07
  • Because the lvalue reference is only returned for lvalues, and thus isn't a problem... – ildjarn Jan 11 '14 at 12:57
  • I'm marking this as the answer, because it summarizes the situation well, and it links to the original proposal. – KnowItAllWannabe Jan 11 '14 at 16:59
13

One use case is to prohibit assignment to temporaries

 // can only be used with lvalues
 T& operator*=(T const& other) & { /* ... */ return *this; } 

 // not possible to do (a * b) = c;
 T operator*(T const& lhs, T const& rhs) { return lhs *= rhs; }

whereas not using the reference qualifier would leave you the choice between two bads

       T operator*(T const& lhs, T const& rhs); // can be used on rvalues
 const T operator*(T const& lhs, T const& rhs); // inhibits move semantics

The first choice allows move semantics, but acts differently on user-defined types than on builtins (doesn't do as the ints do). The second choice would stop the assigment but eliminate move semantics (possible performance hit for e.g. matrix multiplication).

The links by @dyp in the comments also provide an extended discussion on using the other (&&) overload, which can be useful if you want to assign to (either lvalue or rvalue) references.

Community
  • 1
  • 1
TemplateRex
  • 69,038
  • 19
  • 164
  • 304
  • More generally: Prevent exposing a reference or pointer to internal data of a temporary. – dyp Jan 10 '14 at 19:17
  • When would you ever *want* them to be assignable? – user541686 Jan 10 '14 at 19:20
  • @Mehrdad I don't know, but if you don't specify the `&`, they will be. – TemplateRex Jan 10 '14 at 19:22
  • @TemplateRex: Interesting. I feel like this could have been solved by prohibiting an rvalue from being assignable via `= rhs` altogether, instead of by introducing this much more general construct. If people really wanted to assign to rvalues they could just do `.operator=(rhs)` instead. – user541686 Jan 10 '14 at 19:23
  • @Mehrdad that may have been less surprising at the cost of complicating the life of compiler writers :-) – TemplateRex Jan 10 '14 at 19:25
  • @TemplateRex: I mean their life is already complicated by the fact that the two aren't equivalent anyway. You can't do `int.operator=(int)` so it's not like they can blindly transform it into `int = int`, they might as well check this too. – user541686 Jan 10 '14 at 19:38
3

If f() needs a Foo temp that is a copy of this and modified, you can modify the temp this instead while you can't otherwise

Glenn Teitelbaum
  • 10,108
  • 3
  • 36
  • 80
0

On the one hand you can use them to prevent functions that are semantically nonsensical to call on temporaries from being called, such as operator= or functions that mutate internal state and return void, by adding & as a reference qualifier.

On the other hand you can use it for optimizations such as moving a member out of the object as a return value when you have an rvalue reference, for example a function getName could return either a std::string const& or std::string&& depending on the reference qualifier.

Another use case might be operators and functions that return a reference to the original object such as Foo& operator+=(Foo&) which could be specialized to return an rvalue reference instead, making the result movable, which would again be an optimization.

TL;DR: Use it to prevent incorrect usage of a function or for optimization.

Joe
  • 6,497
  • 4
  • 29
  • 55