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.
Certainly was a useful find, thanks for the writup on it. :)
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.