Boost logo

Boost :

Subject: Re: [boost] [Boost.Move] A few notes
From: Jeffrey Lee Hellrung, Jr. (jeffrey.hellrung_at_[hidden])
Date: 2012-01-12 14:16:34

On Thu, Jan 12, 2012 at 5:23 AM, Dan Ivy <danivy.mail_at_[hidden]> wrote:

> On Thu, Jan 12, 2012 at 8:40 AM, Jeffrey Lee Hellrung, Jr.
> <jeffrey.hellrung_at_[hidden]> wrote:
> >
> > On Wed, Jan 11, 2012 at 9:29 AM, Dan Ivy <danivy.mail_at_[hidden]> wrote:
> >
> > > Hi,
> > >
> > > I was recently trying out Boost.Move and a few issues worth sharing
> > > surfaced:
> > >
> > > 1. It would be helpful to have configuration macros to force emulation
> > > mode, even on C++11 compilers, as well as to disable move semantics
> > > altogether (that is, the conversion operators to boost::rv& should be
> > > disabled, and move/forward should return lvalue-references.
> > > and friends should remain intact, so that overloads remain unique.)
> > > In many cases, the higher level semantics of a program are expected to
> be
> > > identical under all three configurations, so having a quick way to
> switch
> > > between them is useful during testing/debugging.
> > >
> >
> > It could be useful to force emulation mode, I suppose, but disabling move
> > semantics altogether could easily break code that uses
> > moveable-but-noncopyable types. So I question the utility of the latter,
> at
> > least.
> >
> Forcing emulation mode is useful for people that primarily use
> compilers where true rvalue-rferences can't be disabled, such as MSVC
> 10+. It wouldn't set a precedence, anyway. Boost.TypeOf, for example,
> already has such a macro, for similiar reasons, I suppose. As for
> disabling move-semantics, I don't feel too strongly about this, and
> personally never really needed it, yet, but I forsee it as coming in
> handy at times. You are correct that it will break some code, this is
> what I was refering to by "in many cases", but move-indifferent code
> is probably the more common one (after all, rvalue-refs are a
> relatively recent feature.) I don't think it's too harmful to just
> have it there, anyway.

I will see what Ion thinks concerning forcing emulation mode. Having
thought about it a little more, it could help to test that code developed
for C++11 also works in C++03, so I wouldn't be opposed to such a switch.
And agreed regarding Boost.TypeOf.

Regarding disabling move semantics, again, I fail to see the utility of
such a global switch. Such a switch will change the semantics of the code
on a global scale. What's wrong with using your own local switch to disable
move semantics for just those classes you're interested in?

> 2. Boost.Move is a little bit too opaque, as it stands. What's really
> > > missing are Boost.Move-aware type traits. Things like
> add_rvalue_reference
> > > are often necessary to calcuate return types of move-aware generic
> > > functions, and so on. Whether this belongs in Boost.TypeTraits or
> > > Boost.Move is a separate question. Likewise, there should be type
> traits to
> > > calculate the return types of boost::move and, in
> > > particular, boost::forward. On C++11, the return type of forward<T>
> > > conincides with add_rvalue_reference<T>, but not so in emulation mode,
> > > hence the necessity for this trait.
> > >
> >
> > +1; perhaps Boost.TypeTraits can update the relevant metafunctions to
> > account for boost::rv<T>&? I think we definitely should have
> >
> > add_reference
> > add_rvalue_reference
> > add_lvalue_reference
> > remove_reference
> > remove_rvalue_reference
> > remove_lvalue_reference
> > is_reference
> > is_rvalue_reference
> > is_lvalue_reference
> >
> > where boost::rv<T>& and (probably) boost::rv<T> const & are treated as
> T&&.
> >
> One thing I forgot to bring up is how these traits shoud handle types
> without move emulation. While it would be nice for the result of
> add_rvalue_reference to be aligned with the return types of
> boost::move and boost::forward, that is, const T&, for non-emulated
> types (which also does the reference-collapsing trick), it wouldn't
> play very nicely with the rest of the traits. (i.e. add_rvalue_ref<T>
> is const T&, is is_rvalue_ref<const T&> true?) This is another good
> reason to provide separate traits to calculate the return types of
> move and forward.

Agreed. So add to the list result_of::move and result_of::forward.

> > > 3. For some reason, the emulated boost::move is written so it doesn't
> > > accept temporaries. This doesn't play too nicely with forwarding:
> > > Unavoidably on C++03, there has to be made a choice whether forwarding
> > > functions use non-const or const-qualified references (bad wording,
> but you
> > > get the point), where the former accepts modifiable lvalues and rejects
> > > temporaries, and the latter does the opposite (well, sort of).
> Boost.Move
> > > chooses the latter. This means that while BOOST_FWD_REF parameters
> accept
> > > temporaries, these temporaries are bound as lvalues, which pretty much
> > > defeats the purpose of the library (note that these temporaries aren't
> > > eligible for copy-elision, since they're bound to a reference.)
> >
> >
> > I wouldn't use such strong language. It's "just" more limited than if
> true
> > rvalue references were available. You can still pass explicitly created
> > emulated rvalue references, and the library will allow you to move them.
> It's not "just" more limited. It's very likely, the way things are
> now, that someone would pass a "raw" (read un-moved) temporary to a
> forwarding function, unaware that it wouldn't be moved (because things
> "just work"). It's even more likely that having been aware of that, he
> would prefer passing the argument through move(), as ugly as it may
> be. This is how I see it, anyway.
> And, as said, boost::move doesn't accept temporaries, so you'll have
> to make a lvalue out of your temporary, and only then move it. Aside
> from being even uglier than using move() directly, how would you do
> that in initializer-lists, for example? (Well, you could use some
> move_temp, or move(temp()) functions, but, you know, how is this any
> better?)

All fair criticisms. However, I think this is the simplest solution. If
BOOST_FWD_REF( T ) expanded to T&, it wouldn't bind to temporaries, making
the call expression ugly, and unnecessarily so in the cases that true
rvalue references are available or the type isn't even movable. In any
event, the current forwarding solution is no worse than what we had before
Boost.Move, so we aren't regressing in any way. If the caller really wanted
to ensure their object got moved, there is a way to do that (bind the
temporary and explicitly move it, or use a macro like BOOST_MOVE below); if
the callee wanted to be particularly helpful, it would offer more overloads
to accurately capture moveable temporaries (this can require up to 4 or
more overloads and SFINAE, but it's possible). Let's not burden the caller
of every call to a BOOST_FWD_REF( T ) parameter.


> > > Ultimately, this is what I suggest:

> > Secondly, seriously
> > > consider changing the definition of BOOST_FWD_REF(T) from const T& to
> T&,
> > > as this would acheive two desirable, IMO, goals::
> > > a. Forwarding functions would accept modifiable lvalues (and keep them
> > > modifiable).
> > > b. Forwarding functions would REJECT temporaries, UNLESS they're passed
> > > through boost::move, which assures that they're treated as rvalues.
> > >
> >
> > This makes the use of the interface defined using BOOST_FWD_REF more
> > onerous, *even* if true rvalue references are available! So I would
> > consider this inferior to the current solution, even if the current
> > solution is more limited. And, sometimes, you don't even know if the
> > expression you're using as a parameter to a function of said interface is
> > an rvalue or an lvalue (and sometimes it could be either, if this is all
> in
> > a template).
> Yes it's uglier (but not THAT ugly). Tough luck, portability comes at
> a price. I'm not sure what you mean by "even if true rvalue references
> are avialable"? The change I propose only has to do with emulation.

But the call expression doesn't change whether emulation is enabled or not.
So you need to uglify the call expression solely to address emulation
shortcomings. I don't think that's bad. Better to uglify the interface


> > I sympathize with your goal, though. I've actually played around with
> > various modifications of Boost.Move during its development process, and I
> > made some suggestions for additions to Boost.Move which were ultimately
> > rejected to keep the library simple (which is totally understandable).
> Two
> > such suggestions were:
> >
> > - An additional macro BOOST_FWD2_REF( T ) which expanded to T& (in
> > emulation mode) and would be used only to capture lvalues or explicitly
> > created (emulated) rvalue references. This included the result of
> > boost::forward.
> I though about suggesting something like this, but came to the same
> conclusion that it overly-complicates stuff. You really don't want to
> have different types of forwarding functions, and having to know which
> is which. It's best to have a single one, however unpleasent the
> consequences are.

In truth, I typically forego BOOST_FWD_REF altogether and just write
different overload sets for "outward-looking" interfaces depending on
BOOST_NO_RVALUE_REFERENCES. In which case BOOST_FWD2_REF is what I end up
using more often.

> - An additional macro BOOST_MOVE( expr ) which expanded to an expression
> > nearly functionally identical to expr except it would explicitly cast the
> > result to an emulated rvalue reference if expr was an rvalue of moveable
> > type.
> >
> +1; I see where you're going with this. I think that, given the issues
> you noted with boost::move taking a reference-to-const, and not
> knowing the r/l-ness of an expression ahead of time, it's a good
> solution. Why was it rejected?

I didn't push for it, merely suggested it. Perhaps Ion can address what he
thought of the idea. Maybe he didn't see the utility of such a macro.

- Jeff

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