Member Function Ref Qualifiers

One of the lesser-known features of C++11 is the fact that you can overload your non-static member functions based on whether the implicit this object parameter is an lvalue reference or an rvalue reference by specifying a functions ref-qualifier. This feature works similar to the way cv-qualifiers work when specifying a method must be called on a const or volatile object, and can in fact be combined with cv-qualifiers.

To specify a ref-qualifier for a member function, you can either qualify the function with & or &&. (The ref-qualifier must come after any cv-qualifiers.) For instance, if you wanted to declare a function to be called on an rvalue reference object only, you would write:

struct S {
  void func() &&;
};

S s1;
s1.func(); // Ill-formed
S().func(); // OK

If you want to overload a function based on the rvalue-ness of the implicit object parameter, you must specify the ref-qualifier for both functions.

struct S {
  void func() &;
  void func() &&;
};

S s1;
s1.func(); // OK, calls S::func() &
S().func(); // OK, calls S::func() &&

Overloading based on a ref-qualifier is useful in (somewhat rare) circumstances where your object can make use of move semantics to reduce expensive construction costs. For instance:

#include <iostream>
#include <utility>

class ExpensiveState {}; // Details unimportant

class Builder {
  ExpensiveState State;

public:
  Builder() = default;
  Builder(const Builder &O) : State(O.State) {
    std::cout << "Copy" << std::endl;
  }
  Builder(Builder &&O) : State(std::move(O.State)) {
    std::cout << "Move" << std::endl;
  }

  Builder operator()() & {
    return Builder(*this);
  }

  Builder operator()() && {
    return Builder(std::move(*this));
  }
};

int main() {
  Builder b;

  b()()()();
}

When executed, this code will output: Copy Move Move Move. The Copy is because b is an lvalue, not an rvalue, and so operator()() & will be called. However, the results of that function are an rvalue, and so the subsequent subexpressions will result in calling operator()() &&. Due to this, resources can be stolen from one invocation to the next on the last three subexpressions, reducing the performance penalties of a copy operation.

In case you are wondering why the std::move(*this) is used when constructing a Builder object; the unary expression *this always results in an lvalue, which would end up calling the copy constructor instead of the move constructor. So the std::move call is required to convert the lvalue into an rvalue.

Ref-qualifiers are not something you will likely use often. However, it is never a bad thing to understand the tools the programming language has to offer. Note: ref-qualifiers are currently supported by clang (tested with 3.4), gcc (tested with 4.9) but not MSVC 2013.

This entry was posted in C/C++ and tagged , , . Bookmark the permalink.

2 Responses to Member Function Ref Qualifiers

  1. Ryan Jones says:

    Certainly was a useful find, thanks for the writup on it. :)

  2. Eric Fowler says:

    I am seeing this:

    stderr: /PlatformSDKClient.cpp:29:5: error: ‘this’ argument to member function ‘send’ is an lvalue, but function has rvalue ref-qualifier
    accessTokenPromise_->send(“”);
    ^
    /libraries/tabby/Signal.h:84:8: note: ‘send’ declared here
    void send(TaskResult result) && {

    AccessTokenPromise_ is initialized thus:

    accessTokenPromise_ = std::make_unique<arvr::tabby::Sender>(std::move(sender));

    And declared thus:
    std::unique_ptr<arvr::tabby::Sender> accessTokenPromise_;

    Now, tell me how to make the error go away. Please.

Leave a Reply

Your email address will not be published. Required fields are marked *