Boost logo

Boost :

Subject: Re: [boost] [move] Reason for BOOST_COPY_ASSIGN_REF?
From: Jeffrey Hellrung (jhellrung_at_[hidden])
Date: 2010-04-21 02:30:38


Chad Nelson wrote:
> On 04/20/2010 11:56 PM, Jeffrey Hellrung wrote:
>
>>> I've been studying Boost.Move, with an eye toward using it to
>>> incorporate move semantics into the XInt library. Most of it looks
>>> pretty straightforward, but I don't understand the reason for the
>>> BOOST_COPY_ASSIGN_REF macro. If you already have a copy-constructor that
>>> takes a reference, what does it give you?
>
> (Sorry, I meant to write operator=, not copy-constructor, there.)
>
>> That's why it's called BOOST_COPY_*ASSIGN*_REF. You use it to define
>> the copy assignment operator. It expands to const T& if
>> BOOST_HAS_RVALUE_REFS, and const rv<T>& otherwise. Boost.Move, in the
>> absence of true rvalue references, depends on copy elision to
>> efficiently construct from rvalues (and reasonably so), so there's no
>> need to do anything special with the copy/move constructor combination.
>
> I'm obviously missing some key concept, because very little of that
> parses. I don't know of any efficient way to determine what that concept
> might be, so here are some possibly-nonsensical questions to try to
> scare it into view:
>
> - - How is the "copy assignment operator" different from the normal
> operator=(const T&)?
>
> - - *Why* does it expand to const T& if if BOOST_HAS_RVALUE_REFS and const
> rv<T>& otherwise? rv<T> is an rvalue representation, if I understand
> correctly, and that isn't equivalent to const T&, is it?
>
> - - What undesirable thing(s) would happen if you ignored
> BOOST_COPY_ASSIGN_REF and simply created a normal operator=(const T&)?

If you look at the definition of the move-enabling macros, you'll see
that a move-enabled class (under C++03) has the following member functions:

operator rv<T>&(); // C1
operator const rv<T>&() const; // C2
T::operator=(T&); // A1
T::operator=(const rv<T>&); // A2
T::operator=(rv<T>&); // A3

The purpose of defining the above array of member functions is to enable
rvalues to bind to the move assignment operator (A3) rather than the
copy assignment operator (A1 and A2). In fact, that's the hardest part
about emulating rvalue references in C++03, and you can still get a good
deal of mileage without it.

To see how it works, consider which operator= overload gets called when
you do

T x(...);
x = f();

If the return type of f() is T&, its result binds to A1 (directly); if
it's const T&, to A2 (via C2); and if it's simply T (so that f() is an
rvalue), it (magically) binds to A3 (via C1). If you were to define a
T::operator(const T&), then rvalues would bind to this overload (which
would be less efficient; we want to move from rvalues, not copy from them).

The move-enabling macro defines A1 to simply forward to A2; A2 is what
the class author implements. The use of the BOOST_COPY_ASSIGN_REF macro
makes it clear what the semantics of A2 ought to be.

Of course, none of this is necessary in the presence of rvalue references.

Hopefully that answers your questions.

>>> Also, how close is it to being official? I'd like to use it, but if
>>> it's not going to be accepted any time soon, I can't justify it.
>> I can't answer the "how close", but you can still use it in the sense
>> that you can adopt its conventions regarding boost::rv, and (if you
>> choose) ignore the rest of the library. These conventions are:
>> - boost::rv<T>& emulates an rvalue reference (and hence may be safely
>> moved from); and
>> - a move-enabled class T provides a non-const conversion operator to
>> boost::rv<T>& and a const conversion operator to const boost::rv<T>&.
>
> Let me rephrase the question. The current Boost.Move library has been
> discussed on this list since at least January of 2009, and it's still
> not approved. So how can I adopt anything regarding boost::rv if the
> library that defines boost::rv might still not be available if/when my
> library is ready for inclusion into a Boost release? Would I have to
> bundle move.hpp with XInt in that case? Or yank out the move stuff
> completely?

I guess you have a couple options. The goal would be to enable a smooth
transition to Boost.Move once accepted:
- Include the move.hpp from the boost sandbox, but only (directly) using
the definition of boost::rv. For the enabling macros, free functions,
etc., use your own implementations (possibly just forwarding to the
sandbox implementations) so that changes to the Boost.Move library
require minimal retooling on your part. This is what I had in mind
originally, but it might not such a good idea as it might cause ODR
violations with other libraries taking this approach. In any case, it
causes issues when trying to distribute your code, unless you're willing
to require users to copy the move library from the sandbox into their
boost directory (life could be worse). Perhaps better is...
- Clone the Boost.Move library in the sandbox into an inner namespace
(xint::detail::rv, xint::detail::move, etc.) and use this "private"
rvalue reference emulation framework, in such a way that when Boost.Move
is accepted into boost, it will be easy to "lift" your private emulation
framework into the boost-standardized one. I know people are generally
against the proliferation of private rvalue reference emulation
frameworks (myself included), but given the lack of any kind of
established convention thus far, it seems like a viable option.

Whether either of the above or whatever else you come up with is worth
the trouble is up to you.

- Jeff


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk