Boost logo

Boost :

Subject: Re: [boost] [non-instrusive move] interest in new library?
From: Thomas Jordan (tomjordan766_at_[hidden])
Date: 2013-01-11 04:58:28


Jeffrey Lee Hellrung, Jr. wrote

>
> On Wed, Jan 9, 2013 at 3:35 AM, THOMAS JORDAN <tomjordan766_at_[hidden]>wrote:
>
>> Apologies, my previous email I sent incomplete by mistake, please ignore
>> that in favour of the following:
>> Hi,
>> Would there be interest for Boost in a small library providing a simple,
>> non-instrusive emulation of basic move semantics for
>> C++03 (though one which could also be used without harm in a C++11
>> environment)?
>>
>> The library currently consists of a small number of 'move' function
>> overloads, plus a trait class. It can be used to facilitate
>> move semantics/efficient value-orientated software design in environments
>> which lack, or forbid C++11 or other, more intrusive
>> C++03-aware move libraries (e.g., old/buggy compiler, learning curve
>> prohibitive, etc.).
>> In conjunction it facilitates/forces copy-elision and return-value (RVO)
>> optimisations in certain situations where they might
>> otherwise not be feasible.
>>
>> It works as follows: it detects at compile-time if the type of the
>> argument(s) has a member swap function, and if so, it
>> performs a swap, otherwise it calls the regular assignment operator ('move
>> if possible, copy otherwise').
>>
>> It can be used to provide an emulation of:
>> - move-assignment between lvalues
>> - moving an rvalue to an lvalue
>> - moving an lvalue to a temporary
>> It also supports moving between built-in arrays.
>>
>> Example use-cases are (ignoring namespace):
>>
>>
>> #include "non_intrusive_move.h"
>>
>>
>> //returning a value
>>
>> std::string foo(std::string s)
>> {
>> //e.g., append something to s
>> //...
>> //compiler can perform rvo (compiler would likely not perform nrvo if
>> just used 'return s;')
>> return move(s);
>> }
>>
>>
>>
>> //moving a value to a function/ctor, when the value is not required by the
>> calling code after the
>> //function/ctor call
>>
>> std::string s("hello");
>> void bar(std::string s){...}
>> bar(s); //copy
>> bar(move(s)); //move
>>
>>
>> //moving a value into place
>>
>> void MyClass::MyClass(std::string s)
>> {
>> //move value from s into default constructed member s_
>> move(s, s_);
>> }
>>
>> Note that the combination of both moving a value to a function/ctor and
>> then moving it into place
>>
>> e.g.,
>> std::string lv;
>> //...
>> MyClass mc(move(lv));
>> //lv no longer needed
>>
>> effectively results in a zero-copy invocation, with just a small number of
>> fixed size swap, default ctor and shallow dtor
>> calls, so this is pretty efficient.
>>
>> //moving a temporary into an lvalue
>> std:: string lv;
>> move(foo(), lv); //nicer than foo().swap(lv)
>>
>> The moved-from value is left in a 'valid' but undefined state, i.e., it can
>> be destructed, assigned/moved to, swapped, etc.
>>
>> The library is non-intrusive and generic, it will perform an efficient move
>> in terms of swap for any type providing a member
>> swap operation, e.g, std::vector, boost::array, user-defined types etc.
>
> You didn't really expound on the implementation, but, if I had to guess,
> you'd also require your types to be default constructible, and it would
> perform suboptimally (worse than leaving the move call out) if the swap
> member function was equivalent to std::swap. I think this might preclude
> its use in generic contexts and limit its use to situations where you
> *know* the type is std::vector-like. Let me know if I'm presuming
> incorrectly :)
>
Yes, it certainly requires the types to be default constructible.

For a type with an expensive swap, performance would be suboptimal. The
library checks
at compile-time whether the element type of the boost array has a swap
member, and
calls array.swap if true, and copies otherwise (recursively if the
element type is also a
boost array). However, the user would need to write an overload for a
type with an
expensive member swap, which precludes the true generic context. So yes,
it is more
aimed at situations where you have pretty firm knowledge of the types
you will be
using. This would either require a really strong caveat emptor or may
not be acceptable
for release into the wild?

The alternative would be to have just the single, default, template move
function:

//pseudo-code
move(T& left, T& right)

plus a pair of template functions to move to and from temporaries
respectively:

T move(T& x)
void move(const T& from, T& to)

then have optimised move functions defined on a per-type basis, e.g.,

MyClass
{
     void swap(MyClass& other){}
     friend void swap(MyClass& a, MyClass& b)
     {
         //efficient swap member
     }
     friend void move(MyClass& lhs, MyClass& rhs)
     {
          lhs.swap(rhs);
     }
}

as well as overloads defined for any cheap-to-swap library types
interested in:

void move(std::vector& lhs, std::vector& rhs){}
void move(boost::function& lhs, boost::function& rhs){}
etc.

Although this is safer in that it should ensure optimality - and is
actually where
I started - it requires more boilerplate to be written, though there
may be
way(s) to automate the generation of the the user-type's move function.
Also, with the library move functions implemented like this,
you could still write algorithms like move()/move_backwards() (and
helper functions
such as iter_move() to move the values between two iterators) generically.

So I guess the new question is, would this even smaller library be of
any interest?
Its use-cases would be the same as described in my original post, just
to reiterate:

//use-case: returning a argument directly from a function

std::string foo(std::string s)
{
      //e.g., append something to s
      //...

      //compiler can perform rvo (compiler would likely not
      //perform nrvo if just used 'return s;')

      return move(s);
}

//use-case: moving a value to a function/ctor, when the value is not
//required by the calling code after the function/ctor call

std::string s("hello");
void bar(std::string s){...}

bar(s); //copy
bar(move(s)); //move
//s has now been moved out of

//use-case: moving a value into place
void MyClass::MyClass(std::string s)
{
       //move value from s into default constructed member s_
       move(s, s_);
}

Note that the combination of both moving a value to a function/ctor and
then moving it into place, e.g.,

std::string lv;
//...

MyClass mc(move(lv));

effectively results in a zero-copy invocation, with just a small number of
fixed size swap, default ctor and shallow dtor calls, so this is pretty
efficient.

//use-case: moving a temporary into an lvalue
std:: string lv;
move(foo(), lv);

> - Jeff
Tom


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