C++11: Rvalue references for *this
Contents
Recently, gcc added support rvalue references for *this
.
(Clang has supported it for quite a while now.)
In this post, I show how to use this feature, and how it means we can finally
define accessors and a few other things like operator=
correctly.
How it used to be
Before C++11, four different versions of non-static member functions could be declared: non-const, const, volatile and const volatile. (Though the volatile versions are barely ever used.)
For example:
struct foo {
void bar(); // [1]
void bar() const; // [2]
};
Overload [1]
can only be called on non-const foo
objects.
However, [2]
can be called on both const and non-const foo
objects.
(But if [1]
exists, it will be used instead of [2]
for non-const objects.)
For example:
foo f(); // function that returns a foo object
foo a;
foo const & b = a;
int main() {
a.bar(); // calls [1]
b.bar(); // calls [2]
f().bar(); // calls [1]
}
Okay, but what’s new?
Since C++11, there are an additional eight versions of a non-static member function
that can be declared: The original four with either &
or &&
added (for example, const &
, or &&
).
If any of the versions you declare are of this new eight, all of them must be.
Let’s take this simple example:
struct foo {
void bar() &; // [1]
void bar() &&; // [2]
void bar() const &; // [3]
};
Here, [1]
can only be called on objects which can be bound to a foo &
.
Similarly, [2]
and [3]
can only be called on objects which you can use as foo &&
or
foo const &
, respectively.
This means that [1]
can not be used for rvalue foo objects,
and similarly [2]
can not be used for lvalues.
Since a foo const &
can bind to both const and non-const lvalues and rvalues,
it will be used for any (non volatile) object that does not match another overload.
(So in this case, with both [1]
and [2]
declared, it will only be used for const objects.)
For example:
foo f(); // function that returns a foo object
foo a;
foo const & b = a;
int main() {
a.bar(); // calls [1]
b.bar(); // calls [3]
f().bar(); // calls [2]
}
Why would I want that?
More often than not, a non-const non-static member function only makes sense for a lvalue.
For instance, it wouldn’t make much sense to call the .clear()
on a rvalue std::string
:
std::string get_name();
get_name().clear(); // This is allowed, but it doesn't make much sense.
If std::string::clear
were declared as &
, this code would not be allowed.
There are quite a few cases in which this feature will come in handy. What follows is a few examples of such cases.
Daisy Chaining
Daisy chaining is a commonly used pattern, which allows you to call multiple member functions in a row:
struct foo {
foo & do_this() { /*...*/ return *this; }
foo & do_that() { /*...*/ return *this; }
foo & and_that() { /*...*/ return *this; }
};
int main() {
foo f;
f.do_this().do_that().and_that();
}
However, this pattern doesn’t work very well for rvalue foo objects:
foo f();
int main() {
f(); // A rvalue
f().do_this().do_that(); // [1] A rvalue or lvalue (reference)?
}
Here, [1]
results in an lvalue reference,
simply because do_that()
is declared to return a lvalue reference.
(So it can turn a rvalue into a lvalue reference! Even static_cast
can’t do that.)
Although this is not a disaster, it can be slightly inconvenient:
int main() {
foo a = f(); // moves
foo b = f().do_this().do_that(); // copies
}
The example can be corrected to work properly for rvalues as follows:
struct foo {
foo & do_this() & { /*...*/ return *this; }
foo && do_this() && {
do_this(); // [1]
return std::move(*this);
}
// ...
};
Here, the first overload is only called on lvalues, and returns an lvalue reference. The second overload is called for rvalues, and thus returns a rvalue reference.
An interesting thing here is that [1]
calls the first (&
) overload.
Although a bit counterintuitive, inside &&
member functions,
*this
is still a lvalue (just like x
is an lvalue inside void f(X && x)
).
Operator =
Rvalues are called ‘r’values because in an assignment expression (a = b
)
they should appear on the right hand side. As you would expect, this does not compile:
int get_age();
int main() {
get_age() = 5; // Error: lvalue required as left operand of assignment
}
It might surprise you, however, that the following example does compile:
std::string get_name();
struct foo { /*...*/ };
foo f();
int main() {
get_name() = "abc"; // No error
foo a;
f() = a; // No error
}
(And even more surprising: Neither gcc nor clang give a warning here.)
The reason this code is allowed is that operator =
for both foo
and std::string
is simply a non-const (pre C++11 style) member function, and thus accepts any
non-const (and non-volatile) object as *this
.
For foo
, this can be fixed by declaring (the default) operator =
only for lvalues:
struct foo {
foo & operator = (foo const &) & = default;
foo & operator = (foo &&) & = default;
};
int main() {
foo a;
f() = a; // Error: no viable overloaded '='
}
There are, as far as I know, no plans to change the default operator= to a &
-member
or to change any of the existing member functions of the standard containers to use this new feature.
Note that the same as for operator= holds for other modifying operators:
int main() {
std::list<int> l;
l.size()++; // Error (.size() gives a fundamental type)
l.begin()++; // OK (.begin() doesn't)
}
Accessors
An accessor is usually a trivial member function that gives you access to a member variable:
class foo {
public:
std::string name;
};
class bar {
std::string name_; // Not public, for some reason.
public:
std::string & name() { return name_; }
std::string const & name() const { return name_; }
};
foo a;
bar b;
Now, b.name()
can be used just like a.name
can: a.name = "x"
, b.name() = "x"
, etc.
As you might expect, however, it does not behave the same with respect to rvalues:
foo f1();
bar f2();
int main() {
f1().name; // [1] This is an rvalue (it is a member of the rvalue f1()).
f2().name(); // [2] Is this also an rvalue?
}
No, because name()
is declared to return a lvalue reference, [2]
is an lvalue, unlike [1]
.
So,
std::string x = f1().name; // Moves
std::string y = f2().name(); // Copies
Solution:
class bar {
std::string name_;
public:
std::string & name() & { return name_; }
std::string && name() && { return std::move(name_); }
std::string const & name() const & { return name_; }
};
int main() {
f1().name; // An rvalue
f2().name(); // An rvalue, too
std::string x = f1().name; // Moves
std::string y = f2().name(); // Moves, too
}
(No, there isn’t a way to declare or define all three accessors at once, unfortunately.)
Other accessors, like operator[]
, should also take rvalues into account:
std::vector<std::string> v();
std::string x = v()[0]; // Copies, doesn't move.
Here, the string is copied, because operator[]
returns a std::string &
,
as it doesn’t have a special overload for &&
to return a std::string &&
.
What about the other one/five?
I mentioned there are eight new ways to overload a member function on the type of *this
,
but all the examples so far only used three: &
, &&
, and const &
. What about the other ones?
Well, four of those are for volatile
objects, which are barely ever used.
If you’ve never made a volatile member function before, it’s likely you’re never going to use it.
So, ignoring all the volatile ones, one remains: const &&
.
I ignored it as well in the examples, because it is a rather useless thing.
It’s an rvalue reference, but you can’t move from it, since it is const.
This means that in almost any case, you’ll handle a const &&
just
like you would handle a const &
, to which any rvalue can bind as well.
Ignoring it made the last example not completely correct, however:
foo const f3(); // function returning a const foo
bar const f4();
int main() {
f3().name; // This is a const &&
f4().name(); // This is a const &
}
So if you want a.name
and b.name()
to have the exact same behaviour,
you should also define a const &&
version.
(And while you’re busy, you might as well make the volatile overloads, too.)
But like I said, since you can’t move from a const &&
anyway,
it doesn’t really matter if you get a const &
instead.
So, defining accessors has gotten a lot more annoying, as we now have to define three/four/eight instead of two/four of them.
I hope that some day, there will be a syntax to support defining all those eight overloads of a member function at once. Maybe something like:
class foo {
std::string name_;
public:
decltype(auto) name() T && { return std::forward<T>(*this).name_; }
}:
Conclusion
Try not to use ‘old style’ non-const (non-static) member functions (void foo()
)
anymore, since those can implicitly bind a rvalue to a lvalue reference.
Most of the time, when you declare a member function void foo()
,
you should’ve used void foo() &
instead,
as most modifying member functions don’t make sense for rvalues.
The same holds for member functions like operator =
and operator ++
.
For const
it doesn’t really matter, as any const (non-volatile) object
can bind to a const &
anyway.
It’s just that if you have void foo() &
, the const
version must also be declared as a reference: void foo() const &
.
In the cases that it does make sense to use a non-const
member function on a rvalue, such as in the case of
an non-const accessor, or when daisy chaining, also make a &&
version.
When there’s no difference between copying and moving your object
(for instance, when it is POD),
it doesn’t really matter whether you get a T&&
or a T&
.
So when that’s the case, and it does make sense to use a non-const member function on rvalues,
you could still use an old style non-const member function to save some typing.