
I have some (long overdue) comments concerning Ion Gaztanaga's proposed Boost.Move library. First, Ion, great job on the documentation and implementation. Both are very readable and usable. These comments stem from my own use and perceived deficiencies in the current state of affairs, but they might also lead to a more complex library. So take them however you want, and you (Ion) and others may respond at your own convenience. In below, when I refer to "C++03 compilers", I mean compilers without rvalue rferences; when I refer to "C++0x compilers", I mean compilers that do have rvalue references. Also, some details are lacking, as I'm writing this kind of haphazardly and over a couple hours while watching the Jazz-Lakers game... -------- boost::rv<T> ------------ The library should clearly define what requirements a class needs to satisfy to work with the library's facilities, specifically when rvalue references are not available. For C++03 compilers, this will involve defining boost::rv<T>, conversion operators to boost::rv<T>& and (if copyable) const boost::rv<T>&, and what constructors and assignment operators there should be and their semantics. I think this interface specification is of primary importance, and the remaining facilities are of secondary importance. boost::is_movable<T> -------------------- This should evaluate to a Boost.MPL integral constant, and it should still work for builtin types (and return false). BOOST_FWD_REF( T ) / boost::forward<T>(x) ----------------------------------------- I think there should be 2 forwarding mechanisms. One is BOOST_FWD_REF( T ) (paired with boost::forward<T>(x)), which expands to const T& on C++03 compilers and is able to capture rvalues. The other I've called BOOST_PERFECT_FWD_REF( T ) (paired with boost::perfect_forward<T>(x)), which expands to T& and will *not* capture rvalues, but will correctly forward lvalue references to non-const. I'm open to alternative names. The intent is that you can use BOOST_PERFECT_FWD_REF( T ) if you know you will never need to capture rvalues (e.g., it will only receive the result of a boost::forward or boost::perfect_forward). I think it might also be useful to provide metafunctions boost::forward_param<T> and boost::perfect_forward_param<T> that strips off any {const} boost::rv<T>&'s for use with Boost.ResultOf. BOOST_MOVABLE_BUT_NOT_COPYABLE / BOOST_COPYABLE_AND_MOVABLE ----------------------------------------------------------- I don't use these. Instead, I've created no fewer than 10 macros to enable move emulation, depending on the particular application: BOOST_BASIC_MOVABLE_COPYABLE( T ) BOOST_MOVABLE_NONCOPYABLE( T ) BOOST_MOVABLE_COPYABLE_DEFINE_COPY_ASSIGN_FROM_MOVE( T ) BOOST_MOVABLE_COPYABLE_DEFINE_COPY_ASSIGN_FROM_SWAP( T ) BOOST_OPTIMAL_MOVABLE_COPYABLE( T ) BOOST_FRIENDLY_MOVABLE_COPYABLE( T ) BOOST_OPTIMAL_MOVABLE_COPYABLE_IF( T, Cond ) BOOST_FRIENDLY_MOVABLE_COPYABLE_IF( T, Cond ) BOOST_OPTIMAL_MOVABLE_COPYABLE_IF_MOVABLE( T, U_seq ) BOOST_FRIENDLY_MOVABLE_COPYABLE_IF_MOVABLE( T, U_seq ) BOOST_BASIC_MOVABLE_COPYABLE provides a move constructor and a move assignment operator, but does not provide a mechanism to move assign automatically from rvalues. I typically use this in base classes. BOOST_MOVABLE_NONCOPYABLE should be self-explanatory. BOOST_MOVABLE_COPYABLE_DEFINE_COPY_ASSIGN_FROM_MOVE( T ) defines a by-value copy assignment via copy-and-move. Similarly, BOOST_MOVABLE_COPYABLE_DEFINE_COPY_ASSIGN_FROM_SWAP( T ) defines a by-value copy assignment via copy-and-swap. Implementing copy assignment to receive by value captures rvalues optimally via copy RVO. BOOST_OPTIMAL_MOVABLE_COPYABLE* and BOOST_FRIENDLY_MOVABLE_COPYABLE* differ in how rvalues are captured for assignment on C++03 compilers. OPTIMAL declares an operator=(T&) that forwards to the copy assignment operator, and an operator=(const rv<T>&) to move assign. As has been discussed previously, this has undesirable consequences if you rely on the compiler to generate the copy assignment operator in a class enclosing an "optimal movable" type. FRIENDLY declares an operator=(T) (on C++03 compilers) and implements it in terms of the move assignment operator. On C++0x compilers, both use an operator=(T&&) and operator(const &). BOOST_*_MOVABLE_COPYABLE_IF( T, Cond ) enables movability if Cond is true, and does not define a move constructor and move assignment operator if Cond is false. Similarly, BOOST_*_MOVABLE_COPYABLE_IF_MOVABLE( T, U_seq ) will enable movability if any type in the Boost.PP seq U_seq is movable (as determined by boost::is_movable). "The rest of the stuff" ----------------------- I'd consider iterator's and algorithm extensions to be of tertiary importance, and I haven't yet used them. - Jeff

Jeffrey Lee Hellrung, Jr. wrote:
I have some (long overdue) comments concerning Ion Gaztanaga's proposed Boost.Move library.
One more thing: A section can be added on overloading to capture rvalues, as long as you know the type (or types) of rvalues you want to capture (this is only difficult in C++03, hence only applies on C++03). The first method to do this is to overload on parameter types T&, const boost::rv<T>&, and boost::rv<T>&. The former two bind to lvalues references to T, and the latter one will bind to rvalues of T: template< class T > struct my_vector { void push_back(T& x) { /* copy x */ } void push_back(const rv<T>& x) { /* copy x */ } void push_back(rv<T>& x) { /* move x */ } }; Clearly, the definitions of push_back(T&) and push_back(const rv<T>&) could be shared (the former can forward to the latter, or they can each call a common implementation function). To make this easy to implement uniformly for both C++03 and C++0x compilers, I've defined metafunctions (actually, I've just extended call_traits) to give me the types lvalue_param_type, const_lvalue_param_type, and rvalue_param_type. These definitions differ depending on if T is movable or not, and on whether the compiler supports rvalue references or not. The downside of the above is the difference in behavior between C++03 compilers and C++0x compilers. Specifically, overloads like void push_back(const T& x) { /* copy x */ } void push_back(T&& x) { /* move x */ } for C++0x are able to induce implicit conversions to T, whereas the 3 C++03 overloads will not be able to induce such conversions. To alleviate this shortcoming, the second method to provide overloads to capture rvalues is use a templated overload that SFINAE-outs any binding to the (known) rvalue type (or types): template< class T > struct my_vector { template< class U > void push_back(U& x) { /* copy x */ } void push_back(rv<T>& x) { /* move x */ } template< class U > typename disable_if< is_same<U,T> >::type push_back(const U& x) { /* copy x */ } }; This is more complicated, but gets closer to the behavior on C++0x compilers. Ion (and others), let me know what you think. Hopefully these last 2 emails generate some discussion. Also, Ion, if there's anything I can do to help out with the development, let me know. - Jeff

El 03/04/2010 20:03, Jeffrey Lee Hellrung, Jr. escribió:
template< class T > struct my_vector { void push_back(T& x) { /* copy x */ } void push_back(const rv<T>& x) { /* copy x */ } void push_back(rv<T>& x) { /* move x */ } };
I use a similar trick in current interprocess containers, which use the other version of the move emulation (the one that does not kick move-assignment for rvalues of copyable types. See push_back here: http://www.boost.org/doc/libs/1_42_0/boost/interprocess/containers/container... Best, Ion

Ion Gaztañaga wrote:
El 03/04/2010 20:03, Jeffrey Lee Hellrung, Jr. escribió:
template< class T > struct my_vector { void push_back(T& x) { /* copy x */ } void push_back(const rv<T>& x) { /* copy x */ } void push_back(rv<T>& x) { /* move x */ } };
I use a similar trick in current interprocess containers, which use the other version of the move emulation (the one that does not kick move-assignment for rvalues of copyable types. See push_back here:
http://www.boost.org/doc/libs/1_42_0/boost/interprocess/containers/container...
Do you think this technique is worth formalizing? I've created a macro that will generate overload sets like this (they look more like your interprocessor push_back's, with templated overloads that are boost::disable_if'ed out if it binds to a given type), but it's pretty general (works for functions, member functions, and constructors with any arity and any number of rvalue types in each positional parameter you wish to capture), hence pretty complicated. It might be useful to provide a macro for just the unary case, and just for member functions (and constructors?). - Jeff

El 05/04/2010 7:22, Jeffrey Lee Hellrung, Jr. escribió:
Ion Gaztañaga wrote:
El 03/04/2010 20:03, Jeffrey Lee Hellrung, Jr. escribió:
template< class T > struct my_vector { void push_back(T& x) { /* copy x */ } void push_back(const rv<T>& x) { /* copy x */ } void push_back(rv<T>& x) { /* move x */ } };
I use a similar trick in current interprocess containers, which use the other version of the move emulation (the one that does not kick move-assignment for rvalues of copyable types. See push_back here:
http://www.boost.org/doc/libs/1_42_0/boost/interprocess/containers/container...
Do you think this technique is worth formalizing?
Interesting if it can simplify code writing for class designers, but it should be quite lightweight, I wouldn't like to maintain the move library simple. Best, Ion

Ion Gaztañaga wrote:
El 05/04/2010 7:22, Jeffrey Lee Hellrung, Jr. escribió:
Ion Gaztañaga wrote:
El 03/04/2010 20:03, Jeffrey Lee Hellrung, Jr. escribió:
template< class T > struct my_vector { void push_back(T& x) { /* copy x */ } void push_back(const rv<T>& x) { /* copy x */ } void push_back(rv<T>& x) { /* move x */ } };
I use a similar trick in current interprocess containers, which use the other version of the move emulation (the one that does not kick move-assignment for rvalues of copyable types. See push_back here:
http://www.boost.org/doc/libs/1_42_0/boost/interprocess/containers/container...
Do you think this technique is worth formalizing?
Interesting if it can simplify code writing for class designers, but it should be quite lightweight, I wouldn't like to maintain the move library simple.
"...I wouldn't like to maintain the move library simple." Come again? - Jeff
participants (2)
-
Ion Gaztañaga
-
Jeffrey Lee Hellrung, Jr.