Boost logo

Boost :

Subject: Re: [boost] [move] new rvalue reference emulation
From: Dan Ivy (danivy.mail_at_[hidden])
Date: 2012-01-26 12:40:47

Sorry for taking so long to respond, I'm a little short in time
lately. Unfortunately, I didn't get to this yet, so I'm just going to
describe more-or-less where it's standing and, in particaular, what
the outstanding issues are, so people could bring up comments or

First of all, in order to hide the implementation details and to make
things portable across 03 an 11, function declarations inevitably have
to be written inside a macro, much like boost.local. I guess Jeffrey's
method would have to end up like that too, to be practical. So, I
assume that we're cool with that.

The range of scenarios I am trying to address is a bit wider than what
discuessed on this thread. It seems that the focus here is on
forwarding functions (which I'll refer to as completely-generic
functions, ones whose parameters are of the form T&, const T&, etc...
in contrast to partially-generic functions (i.e. f(const vector<T>&) )
and simple-functions (i.e. f(const vector<int>&) ). I'm looking for
implicit rvalue detection for the other kinds too, which I suspect are
the more common ones.

For simple functions, I belive I have a non-intrusive solution
wroking. Non-intrusive is to say that it works with any type,
regardless of whether it declares itself as movable or not, much like
true rv-refs are. To stress the usefulness of a non-intrusive
solution, consider the std::string operator+ temporary hell described
in [
Motivation]. Note that you don't actually need to access any of
string's internals to implement the proposed optimization, just the
ability to catch string rvalues, so a non-intrusive solution let's you
write something like this on top of the existing std::string. I've
tried it, and the performance boost is pretty sweet.

Rvalue detection for completely-generic and partially-generic
functions only works for types that inherit from movable<T>, as
illustrated on the other thread (should I provide a link?). IIRC,
Jeffrey was concerned that the inheritance can't be easily disabled
for C++11, but I don't think it should be. Inheriting from movable<T>
"for nothing" is a victimless crime (well *almost*. I'm thinking about
MSVC and EBO).

For partially-generic functions, there's a slight issue with the
lvalue overloads. The basic set of overload for vector<T>, for
instance, looks something like:

template <typename T>
void f(T& lvalue);

template <typename T>
void f(const movable<vector<T> >& rvalue)

The point is that the lvalue overload has to take completely-generic
parameter, so that it won't catch rvalues. Other than making the
actual implementation work extra-hours to make OR work correctly, it
makes calculating the return type tricky (because the deduced T in the
lvalue overload isn't really the T we're looking for). If the
arguments are an exact-match, this is rather simple. Otherwise, it
must use BOOST_TYPEOF for that, which is eh.
On some compilers, it's possible to do better. Instead of using T&
params for lvalues, use const volatile vector<T>&. Temporaries can't
bind to cv-refs, but they will take lvalues of any cv-qualification.
Not all compilers adhere to the rule that temporaries can't bind to
cv-refs, though. (and we can take advantage of this case too! see
later on)

Luckily, completely-generic functions are always an exact match.
Unluckily, they have other issues. In particular, they don't play
nicely with inheritance heirarchies where more than one type inherits
from movable<T>:

struct A : movable<A> {};
struct B : movable<B>, A {};

template <typename T>
void f(const movable<T>&);

If you call f with an A instance, everything's fine. If you call it
with a B instance, though, type deduction is going to fail because T
can be deduced to either A or B, and the language doesn't prefer to
shorter path. Sadly, I can't see a simple way around this. I have
somewhat of a solution, but it scales so badly that I don't want to
propose it. The best that can be done here is to fall-back to the
lvalue overload in case type-deduction fails. That being said, there
are plenty of examples of top-level types (that you wouldn't noramlly
inherit from) that have move semantics and should work fine with this
(i.e. vector, string, etc...).

Lastly, here's my favorite trick for non-intrusive rvalue detection,
that relies on broken compilers that bind temporaries to cv-refs:

template <typename T>
void f(T& lvalue);

template <typename T>
void f(const volatile T& rvalue);

As simple as that! It's only symptom is that it considers
const-volatile lvalues as rvalues. However, const-volatile non-POD
objects, which have special move semnatics, is something I haven't
seen, so I'm not sure it's a real issue (no harm in considering a
volatile int an rvalue, because it has no consequences. It will lose
it's volatility in the context of the function, though). I've tried it
on a few compilers. The interesting ones are those that don't already
have true rv-refs: it works on MSVC 9 and Sun (latest), and fails on
GCC 3.4 (and since this is the correct behavior, I suppose that on any
newer GCC too). It would be nice if people with access to other
compilers without rv-refs give it a go and report the results.

Besides implictly catching rvalues, hiding the functions behind a
macro solves another safety issue with "raw" BOOST_RV_REFs (which at
the very least should be mentioned in the documentation, I don't think
it currently is). BOOST_RV_REF parameters "maintain their rvalue-ness"
in the context of the function. For example:

void danger(const X&);
void danger(BOOST_RV_REF(X));

void naive(BOOST_RV_REF(X) x)

Will call the rv-ref overload on emulation mode, where it should
really call the lvalue one. This is dangerous, and it doesn't happen
with the macro-ed functions.

I hope this isn't too vague and unhelpful :) that's all I have time
for ATM. I'm planning to post some actual code, but this might take a

Boost list run by bdawes at, gregod at, cpdaniel at, john at