$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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