Tim
Tim

Reputation: 703

function && qualifier behaviour

I'm confused by the following code:

struct test {
  void f() & {
    std::cout << "&" << std::endl;
  }
  void f() const& {
    std::cout << "const&" << std::endl;
  }
  void f() && {
    std::cout << "&&" << std::endl;
  }
  void f() const&& {
    std::cout << "const&&" << std::endl;
  }

  void g() & {
    std::cout << "& -> ";
    f();
  }
  void g() const& {
    std::cout << "const& -> " ;
    f();
  }
  void g() && {
    std::cout << "&& -> ";
    f();
  }
  void g() const&& {
    std::cout << "const&& -> ";
    f();
  }

  test() {} //allow default const construction
};

int main(int, char**) {
    test value;
    const test constant;

    value.g();
    constant.g();

    std::move(value).g();
    std::move(constant).g();
}

When I compile with clang 3.5 I get this output:

& -> &
const& -> const&
&& -> &
const&& -> const&

Why is the r-value qualifier being dropped here? And is there any way to call f from g with the right qualifier?

Upvotes: 12

Views: 1212

Answers (4)

Brian Bi
Brian Bi

Reputation: 119124

The call f() is interpreted as (*this).f(). The result of dereferencing a pointer is always an lvalue, so *this is an lvalue and the lvalue-qualified function is called.

This behaviour even makes sense, at least to me. Most of the time the object referred to by an rvalue expression will either be destroyed at the end of the full-expression (a temporary) or at the end of the current scope (an automatic local variable that we choose to std::move). But when you're inside a member function, neither of those is true, so the object should not treat itself as an rvalue, so to speak. This is also why it makes sense for the name of an rvalue reference function parameter to be an lvalue within the function.

If you want the rvalue-qualified f to be called, you can do this:

std::move(*this).f();

Upvotes: 11

Columbo
Columbo

Reputation: 60979

ref-qualifiers affect the implicit object parameter, §13.3.1/4:

For non-static member functions, the type of the implicit object parameter is

  • “lvalue reference to cv X” for functions declared without a ref-qualifier or with the & ref-qualifier
  • “rvalue reference to cv X” for functions declared with the && ref-qualifier

where X is the class of which the function is a member and cv is the cv-qualification on the member function declaration.

Overload resolution is, roughly speaking, performed on the object argument to object parameter conversion just as any other argument->parameter conversion.
However, f() is transformed into (*this).f() (§9.3.1/3), and *this is an lvalue. §5.3.1/1:

The unary * operator performs indirection: […] and the result is an lvalue referring to the object or function to which the expression points.

Hence the overloads of f with the & qualifier are preferred - in fact, the rvalue overloads are entirely ignored since initializers of rvalue object references must be rvalues (last bullet point in §8.5.3/5). Also, non-const references are preferred over const ones in overload resolution (§13.3.3.2/3, last bullet point concerning standard conversions).

Upvotes: 2

Sarfaraz Nawaz
Sarfaraz Nawaz

Reputation: 361352

It is because dereferencing1 this always produces an lvalue, irrespective of the fact that it is pointing to a temporary object or not.

So when you write this:

f();

it actually means this:

this->f(); //or (*this).f()
           //either way 'this' is getting dereferenced here

So the overload of f() which is written for lvalue is invoked from g() — and const-correctness is applied accordingly as well.

Hope that helps.


1. Note that this is a prvalue; only after dereferencing it produces an lvalue.

Upvotes: 3

ixSci
ixSci

Reputation: 13698

Well, for f() the right qualifier is always lvalue reference since you use it on this. All your f calls are nothing else than this->f() and this is always lvalue. It doesn't matter that for the outside world the object is rvalue this is lvalue for the object insides.

Upvotes: 1

Related Questions