Boost logo

Boost :

From: Hamish Mackenzie (boost_at_[hidden])
Date: 2002-02-06 19:37:09


On Wed, 2002-02-06 at 21:47, Howard Hinnant wrote:
> On Wednesday, February 6, 2002, at 10:37 AM, Hamish Mackenzie wrote:
>
> > I have uploaded move.tar.gz which implements some of these ideas.
> > http://groups.yahoo.com/group/boost/files/move.tar.gz
> > I have not had time to test it properly but let me know what you think.
>
> I've taken a quick look at this. I need more time to study it. But
> first impressions are pretty good. One thing that caught my attention
> pretty quickly was several similarities to John Maddock's design that I
> noted at the beginning of this thread (and I think this is a good thing):

I should have mentioned that I saw some of those comments and I have
tried to include as much as possible.

> On Monday, February 4, 2002, at 04:26 PM, Howard Hinnant wrote:
>
> > John Maddock has suggested:
> >
> > T b = std::move(a);
>
> On Monday, February 4, 2002, at 07:40 PM, Howard Hinnant wrote:
>
> > John Maddock has suggested that std::move return the struct
> > std::move_t<T>. Then the class T could create a constructor and
> > assignment operator that accepted a std::move_t<T>. This has a nice
> > parallel with the current requirements of implementing copy semantics.
>
> Your move_from is quite similar to John's move, and your move_source<T>
> is quite similar to John's move_t<T>.

Indeed. I used a different name do differentiate it from move_to and
move_destination< T >

> > One thing that is probably important is adding overloads that take the
> > allocator to use as an argument
> >
> > template< typename T, class Allocator >
> > inline move_construct( T &, Allocator a );
> >
> > etc.
>
> I don't see a need for this. If a class needs an allocator it should
> take it from the source.

Sorry that should have read

template< typename T, class Allocator >
inline move_construct( T *, T &, Allocator a );

I thought it might be important for a container implementor to be able
to pass in the allocator of the container.

> I think I see where are viewpoints are fundamentally different. And
> that is not a particularly bad thing. It is just helpful to recognize:
>
> You are concerned with getting move semantics up and running today, with
> today's compilers and libraries. And that is a laudable goal.

Yes but I also think that we can create an abstraction that would, most
likely, be compatible with future language extensions. Then when the
compiler support is there we can change move_traits or the move*
functions.

This leaves us free to write code that uses move, move_construct etc.
without worrying if it will be compatible with future standards.
 
> I am more concerned with designing move semantics from the ground up for
> inclusion into the next C++ standard. And all parts of the puzzle are
> open to change, including the language, and the std::lib. Backwards
> compatibility is only important so far as ensuring that existing code
> continues to have the same semantics and performs no worse than it does
> today. Since move sematics do not exist today (except for auto_ptr of
> course), we do not have to get existing classes moving. Otoh, if we can
> get existing classes moving for free, then why not take advantage of
> that? I'm still undecided on this issue.
>
> Yes, the /only/ way to move existing std::containers is with swap (or
> maybe clear/splice with list). However I see no way to have vector or
> deque take advantage of this new facility without changing their
> implementation. Since they have to change anyway to use move, I'm not
> overly concerned at this point that they might have to change to be
> moved.

Consider boost::some_container< std::vector< int > > though. If
boost::some_container uses move then the payoff is large because it WILL
use swap( std::vector< int > &, std::vector< int > & ) to move items.

> >> 2. How does one detect if an object implements move semantics (in
> >> templated code)?
> > We could add something to move_traits< T > to indicate how move will
> > behave. It could indicate if move is no-throw. Unfortunately it will
> > require the class designer to provide the information in some way.
>
> Yup and Yup. I dislike a facility that requires class authors to
> register with it, except for the case where the number of objects that
> need to register is known and manageable. For example I can deal with
> having to register all scalars with the move facility. But I dislike
> requiring Joe Programmer to register MyClass with it. It is a source of
> error that could all to easily go undetected (detected only by
> performance monitoring or counting copy constructions for example).

So if move_traits is not specialized what should the default be?
  * use copy construct/assign
  * use swap
  * no move allowed

I am now leaning towards no move allowed (which I think was your
preference). Then specialize move_traits for existing classes that have
a good swap function.

> John Maddock suggested a traits class that would be specialized for
> scalars (actually I think for PODS if is_POD comes on line), but for
> classes it would use
>
> is_convertible<move_source<x>, x>::value
>
> (of course he used move_t, not move_source, but they are the same idea)
>
> This clever technique means that Joe Programmer only needs to provide
> the converting contructor taking move_source<x>, and then x will be
> automatically "registered" with move.

I like it! I will change the default move_traits so that it uses that.
When is_POD is available will add that too (is_scalar is already in
there).

> Unfortunately this will only detect the constructor taking move_source.
> It won't detect move semantics if they are somehow provided "for free",
> except perhaps for PODS. And it won't detect move assignment no matter
> what. The best I currently have for that problem is a rule: If you
> provide move construction, you must also provide move assignment (and
> vice-versa).

That seems like a reasonable assumption. I seem to remember reading a
similar rule for copy constructors and assignment opperators. And the
worst that can happen if you forget "operator =( move_source<T> )"
should be a compiler error.

> >> 3. How does one move from a temporary?
> > Hmm...
> >
> > T f();
> > T dest;
> >
> > move_to( dest ) = mover_from( f() );
> >
> > Will fail to compile (for good reason). But compilers should do a good
> > job with return values so
> >
> > T temp( f() );
> > move_to( dest ) = move_from( temp );
> >
> > Should be just as fast.
>
> I could be wrong, but I think people will complain. auto_ptr went to
> great lengths to insure that you could move from a temporary, but not
> from a const. Indeed, auto_ptr_ref looks remarkably similar to your
> move_source<T>. I don't have the answer for this one. I wish I did.
> Imo, this is the hardest part of move, and may require help from the
> language.

I did try this hack

template< typename T >
move_source< T > move_from_temporary( const T & source )
{
  return move_source< T >( const_cast< T & >( source ) );
}

And on the surface it seemed to work in gcc, but I didn't try it with
optomization on. It feels wrong to me perhaps someone with a copy of
the standard could clarify this.

I don't think we can do what auto_ptr does without changing the return
type of f(), which I don't think is a good idea.

I still think a good compiler should be able to optomize out the copy
constructor from

T temp( f() );
move_to( dest ) = move_from( temp );

> I would much rather see:
>
> void foo(T& source)
> {
> T t(move_from(source));
> }
>
> Or perhaps just:
>
> void foo(T& source)
> {
> T t(move(source));
> }

I would prefer to add the following, but Fernando pointed out that it
needs to use aligned_storage< T > to work and I gather that is not
working yet.

template< class T >
move_to_here
{
  unsigned char buffer_[ sizeof( T ) ];
  T *get() { return reinterperet_cast< T * >( &buffer_ ); }
  const T *get() const { return reinterperet_cast< T * >( &buffer_ );}

public:
  move_to_here() { new ( get() ) (); }
  // not sure if it should have default constructor
  
  explicit move_to_here( T &source )
  {
    move_construct( get(), source );
  }
  ~move_to_here() { allocator< T >::destroy( get() ); }

  T & operator *() { return *get(); }
  const T & operator *() const { return *get(); }

  T * operator -> () { return get(); }
  const T * operator -> () const { return get(); }
};

Then you could do this

move_to_here< T > t( source );

Its still not ideal because you have to use -> and *
 
> Since the author of T may be required to provide the move_source
> constructor anyway (say to avoid the default constructor requirement), I
> am very inclined to /require/ the move_source constructor (except for
> PODS). That would clean up the end user syntax considerably.

The advantage of move_to_here is that it would work for existing classes
(eg. std::vector)

Hamish Mackenzie


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