What's a use case for overloading member functions on reference qualifiers?
Asked Answered
F

4

32

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?

Fresco answered 10/1, 2014 at 19:6 Comment(0)
L
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.

Location answered 10/1, 2014 at 19:49 Comment(4)
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.Everlasting
@Everlasting 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.Location
Because the lvalue reference is only returned for lvalues, and thus isn't a problem...Everlasting
I'm marking this as the answer, because it summarizes the situation well, and it links to the original proposal.Fresco
M
14

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.

Messick answered 10/1, 2014 at 19:13 Comment(6)
More generally: Prevent exposing a reference or pointer to internal data of a temporary.Hyperostosis
When would you ever want them to be assignable?Bughouse
@Mehrdad I don't know, but if you don't specify the &, they will be.Messick
@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.Bughouse
@Mehrdad that may have been less surprising at the cost of complicating the life of compiler writers :-)Messick
@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.Bughouse
E
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

Ennis answered 10/1, 2014 at 19:18 Comment(0)
O
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.

Olgaolguin answered 10/1, 2014 at 21:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.