4

In C++11 and later, one can decorate the member function with &, const&, oe && (or other combinations).

If one has several overloads, and at least one is specified like this, the others must follow the same convention.

Pre C++11 style:

struct A {
   void f() const {}  // #1
   void f()       {}  // #2
};

C++11 and later:

struct  {
   void f() const& {}  // #3
   void f()      & {}  // #4
// void f()     && {}  // if necessary
};

Until today, I though that #4 was equivalent to #2, but today I found a counter example:

struct A {
   void f()      & {}
// void f()     && {}  // commented for testing purposes, see below
}

struct B {
   void f()        {}
}
...
A{}.f();  // compile error: argument discards qualifiers
B{}.f();  // ok

https://godbolt.org/z/qTv6hMs6e

So, what is the deal? An undecorated (non-const) member function written in the old style is equivalent to both its && or & version depending on the context (at the calling point)?

Is the below code the correct interpretation?

struct B {
   void f() {... some body...}
}

...is the same as this?

struct B {
   void f()  & {... some body...}
   void f() && {... some (same) body...}
}

... which is the same as this:

struct B {
   void f()  & {... some body...}
   void f() && {return f();}
}
alfC
  • 14,261
  • 4
  • 67
  • 118

1 Answers1

3

The qualifiers have the exact same meaning as if they were the qualifiers on the hypothetical implicit object parameter which is passed the object expression of the member access expression.

So, #4 can not be called on a prvalue, because a non-const lvalue reference can not bind to a prvalue, explaining why A{}.f(); doesn't work. (A{} is a prvalue)

The old style without reference qualifier is the odd one. It behaves in overload resolution as if the implicit object parameter was an lvalue reference (const or not depending on that qualifier), but in contrast to normal function parameters it is allowed to bind to rvalues anyway for the purpose of overload resolution.

So to replicate the old style unqualified behavior, you need to specify both the &-qualified overload and the &&-qualified overload (at least if the function is not also const-qualified). (There are likely some corner cases where the two qualified member functions are not 100% equivalent to one unqualified one though. I guess a simple example would be trying to take the address &B::f.)

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • @alfC I think the other way around makes more sense. `*this` is always an lvalue, so you can always call the `&` version on it (by just writing `f();`), but to call the `&&` version you have to write `std::move(*this).f()`. – user17732522 Jun 16 '22 at 07:55
  • yes, sorry I wanted to mean to exactly that, that is, or also call the `&` version from the `&&` version. Otherwise one would need an, at best gratuitous, `std::move(*this)`. – alfC Jun 16 '22 at 08:12