[move][container] Review Request (new versions of Boost.Move and Boost.Container in sandbox and vault)

Hi to all, I've just uploaded to boost sandbox... http://svn.boost.org/svn/boost/sandbox/move/) and Boost.Vault... http://www.boostpro.com/vault/index.php?action=downloadfile&filename=boost.move.container.zip&directory=Containers& ...new versions of libraries Boost.Move and Boost.Container. Online documentation can be found here: Boost.Move http://svn.boost.org/svn/boost/sandbox/move/libs/move/doc/html/index.html Boost.Container http://svn.boost.org/svn/boost/sandbox/move/libs/container/doc/html/index.ht... I would like to request a review for both libraries. Boost.Move is quite small (just one header). Boost.Container depends on Boost.Move and Boost.Intrusive so you must download also the post-1.40 version of Intrusive placed in the same package (Boost.Container uses new features from Intrusive not included in Boost 1.40). Just take fresh Boost 1.39 folder and overwrite it with package contents. ************************ What's new in Boost.Move: ************************ ->The library has been modified to support moving rvalues of copyable AND movable types, a limitation of the previous version. This changes where proposed by Klaus Triendl in this thread: http://lists.boost.org/Archives/boost/2009/06/153266.php The library has now new macros to declare copyable and movable types and movable only types. Old macros are still there for backwards compatibility (specially unordered library which was also ported to this move sandbox) but I expect to remove them if current approach is considered better. ->Updated documentation and tests. ************************ What's new in Boost.Container: ************************ -> Added tests and documentation -> Updated the library to the new Boost.Move macros * * * I've tested both libraries in Windows XP using the following compilers: Visual 7.1, Visual 8.0, Visual 9.0, Intel 10.0, Mingw-gcc 4.3 and Mingw-gcc 4.3 in C++0x mode. The library should work on any C++03 conforming compiler Best, Ion

Very great news to hear that this project is moving forward (so to speak ;-)) on Wed Aug 12 2009, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
************************ What's new in Boost.Move: ************************
->The library has been modified to support moving rvalues of copyable AND movable types, a limitation of the previous version. This changes where proposed by Klaus Triendl in this thread:
http://lists.boost.org/Archives/boost/2009/06/153266.php
The library has now new macros to declare copyable and movable types and movable only types. Old macros are still there for backwards compatibility (specially unordered library which was also ported to this move sandbox) but I expect to remove them if current approach is considered better.
->Updated documentation and tests.
http://svn.boost.org/svn/boost/sandbox/move/libs/move/doc/html/move/composit... still says "all moves occur explicitly," but I think you mean "all moves from lvalues," right? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams escribió:
Very great news to hear that this project is moving forward (so to speak ;-))
Let's hope this is the final step!
The library has now new macros to declare copyable and movable types and movable only types. Old macros are still there for backwards compatibility (specially unordered library which was also ported to this move sandbox) but I expect to remove them if current approach is considered better.
->Updated documentation and tests.
http://svn.boost.org/svn/boost/sandbox/move/libs/move/doc/html/move/composit...
still says "all moves occur explicitly," but I think you mean "all moves from lvalues," right?
Yes. The new library now moves non-const rvalues in assignments: copyable_and_movable produce(); copyable_and_movable cm; for(;;){ //now moved instead of copied! cm = produce(); } This is not the case with constructors (the original Klaus Triendl proposal included them, http://lists.boost.org/Archives/boost/2009/06/153266.php) because it terribly uglifies syntax (there is no way in C++03 to automatically generate constructor overloads that forward to another constructor) and because that case is in practice more efficient than moving thanks to RVO: for(;;){ //Copied? No, RVO avoids copy copyable_and_movable cm = produce(); } Best, Ion

on Wed Aug 12 2009, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
David Abrahams escribió:
Very great news to hear that this project is moving forward (so to speak ;-))
Let's hope this is the final step!
The library has now new macros to declare copyable and movable types and movable only types. Old macros are still there for backwards compatibility (specially unordered library which was also ported to this move sandbox) but I expect to remove them if current approach is considered better.
->Updated documentation and tests.
http://svn.boost.org/svn/boost/sandbox/move/libs/move/doc/html/move/composit...
still says "all moves occur explicitly," but I think you mean "all moves from lvalues," right?
Yes. The new library now moves non-const rvalues in assignments:
copyable_and_movable produce();
copyable_and_movable cm; for(;;){ //now moved instead of copied! cm = produce(); }
Only in assignments? what about consume(produce()) scenarios?
This is not the case with constructors (the original Klaus Triendl proposal included them, http://lists.boost.org/Archives/boost/2009/06/153266.php) because it terribly uglifies syntax
Are you sure you've looked at all the alternatives? Every move library I'm aware of before yours supports implicitly move constructing from non-const rvalues.
(there is no way in C++03 to automatically generate constructor overloads that forward to another constructor) and because that case is in practice more efficient than moving thanks to RVO:
for(;;){ //Copied? No, RVO avoids copy copyable_and_movable cm = produce(); }
You might check whether direct initialization is just as efficient as copy initialization before you say that: copyable_and_movable cm((produce())); IIRC EDG-based compilers used to only elide copies in the copy initialization case (!) -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams escribió:
Only in assignments?
what about
consume(produce())
scenarios?
obj produce() { return obj(); } void consume(obj ){} int main() { consume(produce()); return 0; } At least in MSVC 7.1, GCC 4.3 and Intel 10.0 (which is EDG 3.8, http://software.intel.com/en-us/articles/intel-c-compiler-for-windows-genera...) this produces just a default constructor and no calls to copy constructor. And the same happens with: obj o = produce(); and obj o(produce());
This is not the case with constructors (the original Klaus Triendl proposal included them, http://lists.boost.org/Archives/boost/2009/06/153266.php) because it terribly uglifies syntax
Are you sure you've looked at all the alternatives? Every move library I'm aware of before yours supports implicitly move constructing from non-const rvalues.
Well, I can't say I've looked all of them ;-). I think this alternative is one of the most balanced ones, and allows writing optimal code for c++0x compilers without creating temporaries. Regards, Ion

Ion Gaztañaga wrote:
Hi to all,
I've just uploaded to boost sandbox...
http://svn.boost.org/svn/boost/sandbox/move/)
and Boost.Vault...
...new versions of libraries Boost.Move and Boost.Container.
...
Best,
Ion
This is great! I've already gotten a ton of mileage out of what was previously in the sandbox, and it's great that move-assigning from temporaries now works (ifiuc). One small thing: It would be nice if boost::is_movable could be used as an MPL metafunction, i.e., that it have a nested type typedef. For example template<class T> class is_movable { public: static const bool value = ...; typedef boost::integral_constant< value > type; }; This need had come up for me before; I can dig up some context if desired. - Jeff

on Wed Aug 12 2009, Jeffrey Hellrung <jhellrung-AT-ucla.edu> wrote:
Ion Gaztañaga wrote:
Hi to all,
I've just uploaded to boost sandbox...
http://svn.boost.org/svn/boost/sandbox/move/)
and Boost.Vault...
...new versions of libraries Boost.Move and Boost.Container.
...
Best,
Ion
This is great! I've already gotten a ton of mileage out of what was previously in the sandbox, and it's great that move-assigning from temporaries now works (ifiuc).
One small thing: It would be nice if boost::is_movable could be used as an MPL metafunction,
Yeah, nice is an understatement. I insist :-)
i.e., that it have a nested type typedef. For example
template<class T> class is_movable { public: static const bool value = ...; typedef boost::integral_constant< value > type; };
This need had come up for me before; I can dig up some context if desired.
Probably better: template<class T> struct is_movable : mpl::bool_< ... > {}; Cheers, -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Hi! Looks like a great job. Thanks a lot for this. While taking a glance at the docs (very clear and concise!) I had a bad feeling about //Create a list with 10 default constructed objects list<movable> l(10); IMHO container::list's interface should be parallel to STL. std::list<T> does not have such a constuctor. What do you think? Markus

Markus Werle escribió:
Hi!
Looks like a great job. Thanks a lot for this.
While taking a glance at the docs (very clear and concise!) I had a bad feeling about
//Create a list with 10 default constructed objects list<movable> l(10);
IMHO container::list's interface should be parallel to STL. std::list<T> does not have such a constuctor.
http://www.sgi.com/tech/stl/List.html list(size_type n) Creates a list with n elements, each of which is a copy of T(). and from the draft n2857: requires AllocatableElement<Alloc, T> explicit list(size_type n); 3 Effects: Constructs a list with n default constructed elements. 4 Complexity: Linear in n. Best, Ion

Hi! Could you please explain move_backward in more detail? I do not catch its meaning. Markus

Looks good; personally I've been waiting for this so I'm extremely pleased to see the great work Ion has done here! A few small suggestions: Is BOOST_MOVABLE_BUT_NOT_COPYABLE better called BOOST_MOVABLE_AND_NOT_COPYABLE? Or even BOOST_MOVABLE_NOT_COPYABLE or BOOST_MOVABLE_NO_COPY Specifically, why the exceptional 'but' rather than an inclusive 'and'; or just elide the conjunction altogether? Should the limit BOOST_CONTAINERS_MAX_CONSTRUCTOR_PARAMETERS use BOOST_CONTAINER_... (ie, drop the pluralising 'S')? Similarly for other pre-processor symbols; some use BOOST_CONTAINERS_ and others use BOOST_CONTAINER_ I note that there is a containers_detail namespace; since this this not part of the public interface there is no case for renaming this to container_detail other than internal consistency. The stable_vector is nice (same as chain<T>), just some of the pre-processor symbols don't have BOOST_ prefixes, and CONTAINERS_ vs. CONTAINER_ comes up again. This is niggly, sure. I'm pleased that Ion has taken the time to do this, and do it well. Now I can finish monotonic with both stateful and stateless allocators. Christian

Christian Schladetsch wrote:
Looks good; personally I've been waiting for this so I'm extremely pleased to see the great work Ion has done here!
A few small suggestions:
Is BOOST_MOVABLE_BUT_NOT_COPYABLE better called BOOST_MOVABLE_AND_NOT_COPYABLE? Or even BOOST_MOVABLE_NOT_COPYABLE or BOOST_MOVABLE_NO_COPY
BOOST_MOVABLE_ONLY?

Thank you very much for your effort. Is it planned to provide a move-aware pair type? If not, then how to insert an element into the map of movable-only values?

Ilya Sokolov escribió:
Thank you very much for your effort.
Is it planned to provide a move-aware pair type? If not, then how to insert an element into the map of movable-only values?
See container/detail/pair.hpp. It was needed to implement some internal features, maybe it could serve as an starting point. Best, Ion

Ion Gaztañaga wrote:
I've just uploaded to boost sandbox [...] new versions of libraries Boost.Move and Boost.Container. [...]
I would like to request a review for both libraries. Boost.Move is quite small (just one header).
Wouldn't it be best to put on the fast-track review queue even? Boost.Container is just a wrapper on top of Boost.Intrusive with the interfaces the C++0x standard draft mandates right?

Mathias Gaunard escribió:
Ion Gaztañaga wrote:
I've just uploaded to boost sandbox [...] new versions of libraries Boost.Move and Boost.Container. [...]
I would like to request a review for both libraries. Boost.Move is quite small (just one header).
Wouldn't it be best to put on the fast-track review queue even?
Boost.Container is just a wrapper on top of Boost.Intrusive with the interfaces the C++0x standard draft mandates right?
Right (well, the draft changes so quickly, that I can guarantee that ;-)) I think it could be easy with Move, because it's just one header. Containers contain much more code, but most of them where already reviewed for Interprocess (including flat_xxx). I'll post a request. Best, Ion

Ion Gaztañaga wrote:
...new versions of libraries Boost.Move and Boost.Container.
Few comments on proposed Boost.Move: - Make all metafunctions true MPL metafunctions (as mentioned in previous postings). - BOOST_COPYABLE_AND_MOVABLE definition scopes relative to the global scope, while other macros seems to scope relative to the local scope (e.g., the difference between ::boost::rv<TYPE> and boost::rv<TYPE>). I'm a stickler for consistency, and would probably consider ::boost::rv<TYPE> et al to be safer. - What is the rationale for placing BOOST_COPYABLE_AND_MOVABLE and BOOST_MOVABLE_BUT_NOT_COPYABLE in the private section of a class definition? IIRC, BOOST_ENABLE_MOVE_EMULATION was suppose to go in the public section, and I'm generally wondering what the motivations are for specifying public or private. - Even though this can be gleaned from the code, I'd like to see a "How it works" section, e.g., how exactly does the library coerce rvalues into binding to the BOOST_RV_REF overload of operator= rather than the BOOST_COPY_ASSIGN_REF overload? Perhaps more generally, I think some rationale for why this particular emulation strategy was chosen over others would be nice (e.g., the previous proposed Boost.Move used a by-value operator= to assign from rvalues efficiently; why the change? How does this compare to Adobe's move emulation?). In most other respects, I think the documentation is very good and complete. - I would've renamed BOOST_COPY_REF_N_TEMPL_ARGS to BOOST_COPY_ASSIGN_REF_N_TEMPL_ARGS just to be consistent with BOOST_RV_REF and BOOST_RV_REF_N_TEMPL_ARGS. - Can you comment at all on the stability of the names of the "class declaration" macros (e.g., BOOST_MOVABLE_BUT_NOT_COPYABLE)? I recall a previous post that suggested alternative names. I have no opinion either way, I was just curious on the status of that suggestion. - In the clone_ptr example, would it be more correct to check for self-assignment in the move assignment operator (as is done in the copy assignment operator) rather than not? - Overall, great job. If there's anything I can do to help the transition, let me know. - Jeff

Jeffrey Hellrung escribió:
Ion Gaztañaga wrote:
...new versions of libraries Boost.Move and Boost.Container.
Few comments on proposed Boost.Move:
Thanks for the comments!
- Make all metafunctions true MPL metafunctions (as mentioned in previous postings).
Ok, but I would like to avoid any header dependency with MPL so that library is minimal.
- BOOST_COPYABLE_AND_MOVABLE definition scopes relative to the global scope, while other macros seems to scope relative to the local scope (e.g., the difference between ::boost::rv<TYPE> and boost::rv<TYPE>). I'm a stickler for consistency, and would probably consider ::boost::rv<TYPE> et al to be safer.
Yes, thanks.
- What is the rationale for placing BOOST_COPYABLE_AND_MOVABLE and BOOST_MOVABLE_BUT_NOT_COPYABLE in the private section of a class definition? IIRC, BOOST_ENABLE_MOVE_EMULATION was suppose to go in the public section, and I'm generally wondering what the motivations are for specifying public or private.
I just wanted to maintain public part as clean as possible, but there is no strong argument for that.
- Even though this can be gleaned from the code, I'd like to see a "How it works" section, e.g., how exactly does the library coerce rvalues into binding to the BOOST_RV_REF overload of operator= rather than the BOOST_COPY_ASSIGN_REF overload? Perhaps more generally, I think some rationale for why this particular emulation strategy was chosen over others would be nice (e.g., the previous proposed Boost.Move used a by-value operator= to assign from rvalues efficiently; why the change? How does this compare to Adobe's move emulation?). In most other respects, I think the documentation is very good and complete.
Ok, I'll add this.
- I would've renamed BOOST_COPY_REF_N_TEMPL_ARGS to BOOST_COPY_ASSIGN_REF_N_TEMPL_ARGS just to be consistent with BOOST_RV_REF and BOOST_RV_REF_N_TEMPL_ARGS.
Yes, thanks.
- Can you comment at all on the stability of the names of the "class declaration" macros (e.g., BOOST_MOVABLE_BUT_NOT_COPYABLE)? I recall a previous post that suggested alternative names. I have no opinion either way, I was just curious on the status of that suggestion.
I don't have any name preference, this can be discussed in the review so that we find a suitable name.
- In the clone_ptr example, would it be more correct to check for self-assignment in the move assignment operator (as is done in the copy assignment operator) rather than not?
Ok.
- Overall, great job. If there's anything I can do to help the transition, let me know.
- Jeff
Thanks! Ion

Ok, but I would like to avoid any header dependency with MPL so that library is minimal. Just jumping in on this. To have your meta-function MPL compliant, I
Ion Gaztañaga wrote: think you may just need them to export their result type as a typedef named type or as a internal template class named apply. This is ofc doesn't require any mpl header. -- ___________________________________________ Joel Falcou - Assistant Professor PARALL Team - LRI - Universite Paris Sud XI Tel : (+33)1 69 15 66 35

Ion Gaztañaga wrote:
Jeffrey Hellrung escribió:
Ion Gaztañaga wrote: - What is the rationale for placing BOOST_COPYABLE_AND_MOVABLE and BOOST_MOVABLE_BUT_NOT_COPYABLE in the private section of a class definition? IIRC, BOOST_ENABLE_MOVE_EMULATION was suppose to go in the public section, and I'm generally wondering what the motivations are for specifying public or private.
I just wanted to maintain public part as clean as possible, but there is no strong argument for that.
I'm not familiar with this library, so I don't know what those macros do. If they're used to define a type as movable, they should be in the public interface, as a type's movability is often a complexity guarantee (especially for containers where falling back to a copy would be a change from constant to linear time). If they just declare scaffolding, they should be private. Either way, their names ought to reflect what they do. (If it generates implementation, put BOOST_MOVEABLE_NOT_COPYABLE_IMPL or something; otherwise the current names are probably fine.) --Jeffrey Bosboom

Ion Gaztañaga wrote:
Thanks for the comments!
No problem.
- Make all metafunctions true MPL metafunctions (as mentioned in previous postings).
Ok, but I would like to avoid any header dependency with MPL so that library is minimal.
That's fair, though as noted by others, all you need to do us add a nested type typedef which has a nested value constant, which (I think) can even be the metafunction class itself. You'd still keep the value bool constant in the original class as well. Example (not 100% sure this is correct): template< class T > struct metaf { // Or use BOOST_STATIC_CONSTANT( ... ) ? static const bool value = ...; typedef metaf type; }; - Jeff

Ion, With your current move-emulation approach, it looks like functions like vector::push_back could actually capture genuine rvalues, similar to how operator= does, via BOOST_COPY_ASSIGN_REF( T ) for movable types T (and using const T& for nonmovable types). For example: template< class T > T factory() { ... } template< class T, ... > struct vector { ... typedef boost::mpl::if_< boost::is_movable<T>, BOOST_COPY_ASSIGN_REF( T ), const T& >::type const_lvalue_reference_type; ... push_back(const_lvalue_reference_type value); push_back(BOOST_RV_REF( T ) value); }; ... vector<T> v; v.push_back(factory()); // should call rvalue push_back, right? AFAIK, if you use push_back(const T&) for movable types (as is standard), then that overload would be preferred in the absence of true rvalue references. What do you think? - Jeff

Jeffrey Hellrung escribió:
Ion,
With your current move-emulation approach, it looks like functions like vector::push_back could actually capture genuine rvalues, similar to how operator= does, via BOOST_COPY_ASSIGN_REF( T ) for movable types T (and using const T& for nonmovable types). For example:
Yes, I know that, but I had no time to update containers to take advantage of this feature. I'll try to do it before the review, I think you need 3 overloads for C++03 systems: //const T & for C++0x, const boost::rv<T> & for C++03 push_back(BOOST_COPY_ASSIGN_REF(value_type)v); //T && for C++0x, boost::rv<T> & for C++03 push_back(BOOST_RV_REF( T ) value); #ifdef BOOST_HAS_RVALUE_REFS push_back(value_type & v) { push_back(const_cast<const value_type &>(v); } #endif However, I haven't found a way to generalize it for perfect forwarding (emplace, emplace_back) because the template parameters must be deduced from arguments and that does not work as push_back. I need help from more talented people ;-) Best, Ion

Ion Gaztañaga wrote:
Jeffrey Hellrung escribió:
Ion,
With your current move-emulation approach, it looks like functions like vector::push_back could actually capture genuine rvalues, similar to how operator= does, via BOOST_COPY_ASSIGN_REF( T ) for movable types T (and using const T& for nonmovable types). For example:
Yes, I know that, but I had no time to update containers to take advantage of this feature. I'll try to do it before the review, I think you need 3 overloads for C++03 systems:
//const T & for C++0x, const boost::rv<T> & for C++03 push_back(BOOST_COPY_ASSIGN_REF(value_type)v); //T && for C++0x, boost::rv<T> & for C++03 push_back(BOOST_RV_REF( T ) value);
#ifdef BOOST_HAS_RVALUE_REFS push_back(value_type & v) { push_back(const_cast<const value_type &>(v); } #endif
However, I haven't found a way to generalize it for perfect forwarding (emplace, emplace_back) because the template parameters must be deduced from arguments and that does not work as push_back. I need help from more talented people ;-)
Best,
Ion
Great, just making sure that was on the table ;) Also, surely you mean #ifndef BOOST_HAS_RVALUE_REFS // rather than #ifdef ? I could be confused... As far as generic forwarding, I've been playing around with it a little and I'm likewise stuck on how to capture true rvalues via template parameter deduction :/ - Jeff

Jeffrey Hellrung escribió:
Great, just making sure that was on the table ;) Also, surely you mean
#ifndef BOOST_HAS_RVALUE_REFS // rather than #ifdef
Yes ;-)
As far as generic forwarding, I've been playing around with it a little and I'm likewise stuck on how to capture true rvalues via template parameter deduction :/
It's not trivial... And if we more than 2 overloads for each parameter, we'll need to produce a ton of preprocessed code ;-) Best, Ion

Ion Gaztañaga wrote:
It's not trivial... And if we more than 2 overloads for each parameter, we'll need to produce a ton of preprocessed code ;-)
Best,
Ion
I've used Boost.Preprocessor to generate 2^n overloads of T&/const T& combinations (based on a post here not too long ago) (when BOOST_HAS_RVALUE_REFS isn't defined) and some additional metaprogramming to capture explicitly generated rvalues (from boost::move), so I think the only thing that's missing is capturing automatically generated rvalues. Right now I hack it with a force_move free function that const_cast/static_cast's its argument to rv<T>& and apply it to auto rvalues. - Jeff

AMDG Mathias Gaunard wrote:
Jeffrey Hellrung wrote:
I've used Boost.Preprocessor to generate 2^n overloads of T&/const T&
There should be no need to write such a thing yourself, as Boost.Functional/Forward already does it.
This only works for function objects. In Christ, Steven Watanabe

Mathias Gaunard wrote:
Jeffrey Hellrung wrote:
I've used Boost.Preprocessor to generate 2^n overloads of T&/const T&
There should be no need to write such a thing yourself, as Boost.Functional/Forward already does it.
I can't find this component in the boost documentation section, a quick google search, sandbox svn repo, or vault, and searching for "forward" in the boost source doesn't look like it gives anything useful. Help? OvermindDL1 wrote:
2009/8/28 Ion Gaztañaga <igaztanaga@gmail.com>:
It's not trivial... And if we more than 2 overloads for each parameter, we'll need to produce a ton of preprocessed code ;-)
I thought Boost.Fusion could ease most of that without preprocessed code?
If you're willing to force the client to wrap arguments up into a fusion vector, correct? I only took a superficial look at the fusion/functional section of the fusion docs, assuming that's what you're referring to. - Jeff

AMDG Jeffrey Hellrung wrote:
Mathias Gaunard wrote:
Jeffrey Hellrung wrote:
I've used Boost.Preprocessor to generate 2^n overloads of T&/const T&
There should be no need to write such a thing yourself, as Boost.Functional/Forward already does it.
I can't find this component in the boost documentation section, a quick google search, sandbox svn repo, or vault, and searching for "forward" in the boost source doesn't look like it gives anything useful. Help?
It's in the trunk but has never been merged to the release branch. In Christ, Steven Watanabe

2009/8/28 Ion Gaztañaga <igaztanaga@gmail.com>:
Jeffrey Hellrung escribió:
Great, just making sure that was on the table ;) Also, surely you mean
#ifndef BOOST_HAS_RVALUE_REFS // rather than #ifdef
Yes ;-)
As far as generic forwarding, I've been playing around with it a little and I'm likewise stuck on how to capture true rvalues via template parameter deduction :/
It's not trivial... And if we more than 2 overloads for each parameter, we'll need to produce a ton of preprocessed code ;-)
I thought Boost.Fusion could ease most of that without preprocessed code?

After looking at move/boost/move/move.hpp, I was interested in how movable types are actually supposed to behave, so I decided to give move/boost/container/vector.hpp a mini review (I just searched for "BOOST_" and "move", and reviewed the code I found that way): //! <b>Effects</b>: Move constructor. Moves mx's resources to *this. //! //! <b>Throws</b>: If allocator_type's copy constructor throws. //! //! <b>Complexity</b>: Constant. vector(BOOST_RV_REF(vector) mx) : base_t(boost::move(mx)) { this->swap(mx); } I would say that if an automatic "bad smell" detection tool like Coverity would flag the above code as dangerous, it wouldn't be completely wrong. After mx was used via boost::move(mx), it is not supposed to be used again. So I think it would be better to write vector(BOOST_RV_REF(vector) mx) : base_t(mx) { this->swap(mx); } which is also less confusing for a human reader. //! <b>Effects</b>: Move assignment. All mx's values are transferred to *this. //! //! <b>Postcondition</b>: x.empty(). *this contains a the elements x had //! before the function. //! //! <b>Throws</b>: If allocator_type's copy constructor throws. //! //! <b>Complexity</b>: Constant. vector& operator=(BOOST_RV_REF(vector) x) { if (&x != this){ this->swap(x); x.clear(); } return *this; } I would argue that the implementation is not consistent with the documentation. The x.clear() statement can cause up to n destructors to be called, violating the "<b>Complexity</b>: Constant." claim. The x.clear() statement also has a bad interaction with the "has_trivial_destructor_after_move" template specialization at the end of the vector.hpp file: namespace boost { //!has_trivial_destructor_after_move<> == true_type //!specialization for optimizations template <class T, class A> struct has_trivial_destructor_after_move<boost::container::vector<T, A> > { static const bool value = has_trivial_destructor<A>::value; }; } This code claims that boost::container::vector has a trivial destructor after move if the allocator has a trivial destructor after move. But this is not true, since x.clear() only called the destructors of the contained objects and set the vector size to 0, but m_start and m_capacity are still unchanged and the memory of the vector is still allocated. So a fixed version could look like //! <b>Postcondition</b>: x.empty() && !x.capacity(). *this contains a the elements x had //! before the function. ... //! <b>Complexity</b>: Linear to the number of elements in the vector (destructor calls). vector& operator=(BOOST_RV_REF(vector) x) { if (&x != this){ this->swap(x); x.clear(); shrink_to_fit(); } return *this; } But one can wonder whether it would not be better to completely omit the x.clear() statement. After all, x is ("equivalent to") an r-value, so it can be treated as if it will be destroyed immediately after the function call. It's the responsibility of the user that tags an function argument as r-value by using boost::move to ensure that it can be treated just like an r-value. As you probably remember, I wondered whether I'm allowed to implement my move assignment operators as a simple swap. The answer of Frank Mori Hess convinced me that I'm allowed to do this. Frank Mori Hess wrote:
Thomas Klimpel wrote:
I hope I can omit the temporary, because two calls to swap are less efficient than a single call to swap. I can't find any second thought that forces me to create a temporary. Do I miss something?
I don't think you need to. The temporary is useful if you want to always give moved-from objects a specific state (presumably the move constructor does this). But the standard is looser, it only requires the moved-from object doesn't violate any of the class invariants.
The argument against this was that it might be quite surprising to the user. Ion Gaztañaga wrote:
For some types (shared_ptr, containers...) a simple swap can lead to unexpected behavior because the programmer might not expect any value transference:
shared_ptr<X> a(...), b(..); //Should b take ownership of a's resources? //That might be quite surprising. a = boost::move(b);
So my position is that the user better be prepared to expect that "a = boost::move(b); " can be implemented as "a.swap(b)", because move assignment is about efficiency, and a.swap(b) is often the most efficient implementation of move assignment. I'm still looking forward for Boost.Move being scheduled for review. Regards, Thomas

Thomas Klimpel wrote:
I would say that if an automatic "bad smell" detection tool like Coverity would flag the above code as dangerous, it wouldn't be completely wrong. After mx was used via boost::move(mx), it is not supposed to be used again. So I think it would be better to write
vector(BOOST_RV_REF(vector) mx) : base_t(mx) { this->swap(mx); }
which is also less confusing for a human reader.
If base(const base_t & rhs) copies rhs into this and if swap(...) swaps everything (including base_t part of a verctor) then the base_t part of mx will be first copied into this and then swaped back into mx. I do not think this can cause errors unless there is something special about slicing a vector instance, but the copy seems to be redundant.

Ilya Bobir a écrit :
Thomas Klimpel wrote:
I would say that if an automatic "bad smell" detection tool like Coverity would flag the above code as dangerous, it wouldn't be completely wrong. After mx was used via boost::move(mx), it is not supposed to be used again. So I think it would be better to write
vector(BOOST_RV_REF(vector) mx) : base_t(mx) { this->swap(mx); }
which is also less confusing for a human reader.
If base(const base_t & rhs) copies rhs into this and if swap(...) swaps everything (including base_t part of a verctor) then the base_t part of mx will be first copied into this and then swaped back into mx.
I do not think this can cause errors unless there is something special about slicing a vector instance, but the copy seems to be redundant.
This depends on what operations moved-from objects support.

Thomas Klimpel escribió:
I would say that if an automatic "bad smell" detection tool like Coverity would flag the above code as dangerous, it wouldn't be completely wrong. After mx was used via boost::move(mx), it is not supposed to be used again. So I think it would be better to write
vector(BOOST_RV_REF(vector) mx) : base_t(mx) { this->swap(mx); }
which is also less confusing for a human reader.
Yes, but that wouldn't be the right thing. When writing move constructors one must first move base classes and then members.Otherwise, the base it's copied instead of moved. Anyway, I think this should be reflected better casting mx to base_t and then moved.
I would argue that the implementation is not consistent with the documentation. The x.clear() statement can cause up to n destructors to be called, violating the "<b>Complexity</b>: Constant." claim.
You are right.
The x.clear() statement also has a bad interaction with the "has_trivial_destructor_after_move" template specialization at the end of the vector.hpp file:
You are right. clear() was added later in the design so there are some flaws regarding these issues. Thanks for spotting this.
So a fixed version could look like
//! <b>Postcondition</b>: x.empty() && !x.capacity(). *this contains a the elements x had //! before the function. ... //! <b>Complexity</b>: Linear to the number of elements in the vector (destructor calls). vector& operator=(BOOST_RV_REF(vector) x) { if (&x != this){ this->swap(x); x.clear(); shrink_to_fit(); } return *this; }
But one can wonder whether it would not be better to completely omit the x.clear() statement. After all, x is ("equivalent to") an r-value, so it can be treated as if it will be destroyed immediately after the function call. It's the responsibility of the user that tags an function argument as r-value by using boost::move to ensure that it can be treated just like an r-value.
This can lead to suprissing behaviours in the presence of some reference counted values: vector<shared_ptr<T>> v1 (...); shared_ptr<T> t1(new T(...)); v1.push_back(move(t1)); vector<shared_ptr<T>> v2 (...); shared_ptr<T> t2(new T(...)); v2.push_back(move(t2)); //.... //transfer t2 pointee to v2 v1 = move(v2); //..but t1's pointee is still alive. The programmer has not required any value swapping just a "transfer" and I think the programmer expects all previous v1 values should have been destroyed. This has been discussed before, but I agree that we should have a consensus on these issues. Maybe committee members have some guidelines for move semantics.
I'm still looking forward for Boost.Move being scheduled for review.
Still waiting... ;-) Thanks for our comments, Best, Ion

-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 On Monday 24 August 2009, Ion Gaztañaga wrote:
Thomas Klimpel escribió:
I would say that if an automatic "bad smell" detection tool like Coverity would flag the above code as dangerous, it wouldn't be completely wrong. After mx was used via boost::move(mx), it is not supposed to be used again. So I think it would be better to write
vector(BOOST_RV_REF(vector) mx) : base_t(mx) { this->swap(mx); }
which is also less confusing for a human reader.
Yes, but that wouldn't be the right thing. When writing move constructors one must first move base classes and then members.
Yes, but the swap swaps both the base members and derived members.
Otherwise, the base it's copied instead of moved. Anyway, I think this should be reflected better casting mx to base_t and then moved.
Wouldn't it look less suspicious if base_t were constructed the same way as in the default vector constructor? In this case moving into the base class can't accomplish anything useful even in principle, since whatever it does will be blown away by the swap. And if a move constructor were added to base_t (detail::vector_alloc_holder doesn't appear to have one) which modified its moved-from argument, it wouldn't cause bugs. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) iEYEARECAAYFAkqSn/QACgkQ5vihyNWuA4VOXwCdEt/03LF7JmtHH8CQu4VBOFsl 0R8AnRCooy6YsQJogwaa2I6NRL3WI+eX =3nqR -----END PGP SIGNATURE-----

Frank Mori Hess escribió:
-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1
On Monday 24 August 2009, Ion Gaztañaga wrote:
Thomas Klimpel escribió:
I would say that if an automatic "bad smell" detection tool like Coverity would flag the above code as dangerous, it wouldn't be completely wrong. After mx was used via boost::move(mx), it is not supposed to be used again. So I think it would be better to write
vector(BOOST_RV_REF(vector) mx) : base_t(mx) { this->swap(mx); }
which is also less confusing for a human reader. Yes, but that wouldn't be the right thing. When writing move constructors one must first move base classes and then members.
Yes, but the swap swaps both the base members and derived members.
Ooops! Thanks for spotting this.
Wouldn't it look less suspicious if base_t were constructed the same way as in the default vector constructor? In this case moving into the base class can't accomplish anything useful even in principle, since whatever it does will be blown away by the swap. And if a move constructor were added to base_t (detail::vector_alloc_holder doesn't appear to have one) which modified its moved-from argument, it wouldn't cause bugs.
Yes, default constructor would be right, the problem is that an allocator might not have a default constructor. I'll fix this. Thanks, Ion

Thomas Klimpel escribió:
So my position is that the user better be prepared to expect that "a = boost::move(b); " can be implemented as "a.swap(b)", because move assignment is about efficiency, and a.swap(b) is often the most efficient implementation of move assignment.
Some additional discussion can be found in this thread: http://lists.boost.org/Archives/boost/2007/04/119872.php I was also against clear(), but somehow people convinced me and I added clear() to my classes ;-) Best, Ion

Ion Gaztañaga wrote:
Thomas Klimpel escribió:
So my position is that the user better be prepared to expect that "a = boost::move(b); " can be implemented as "a.swap(b)", because move assignment is about efficiency, and a.swap(b) is often the most efficient implementation of move assignment.
Some additional discussion can be found in this thread:
http://lists.boost.org/Archives/boost/2007/04/119872.php
I was also against clear(), but somehow people convinced me and I added clear() to my classes ;-)
I have read that thread now. I didn't have the impression that this thread really reached a conclusion. I like the idea of Howard Hinnant to examine the (generic) algorithms that work with move semantics to determine how move-construction and move-assignment should behave. However, the example he gives why he prefers the semantics of clear(); swap(); is not a (generic) algorithm but an example with concrete types: Howard Hinnant wrote:
Now consider vector<thread>: vector<thread> v1; ... vector<thread> v2; ... v1 = std::move(v2); After the move assign, I think it best that whatever threads v1 happened to previously refer to, are now busy canceling themselves, instead of continuing to run under v2's ownership. If you want the latter, you've got swap.
If the user wants to ensure that the threads get canceled, he can call "v2.clear();" himself. So the question for me would be whether there are (generic) algorithms (because they can't call x.clear() themselves) that will use more resources if the move-assignment doesn't call x.clear() for them. After all, those (generic) algorithms are the real clients that will call std::move (x) and will have to deal with non-temporary moved from objects.
The programmer has not required any value swapping just a "transfer" and I think the programmer expects all previous v1 values should have been destroyed.
To be honest, it is exactly because of this argument why I don't like the clear() in the assignment operator of vector. The words "The only requirement is that the object remain in a self consistent state (all internal invariants are still intact)." don't forbid to implement move-assignment as swap, so if the programmer expects something incompatible with this implementation, he is relying on undefined behavior. The move-constructor and move-assignment must be implemented for every class that wants to implement move semantics, so it's best not to burden these with unnecessary requirements and expectations. On the other hand, there might be other good reasons to call clear() in the assignment operator, or more generally try to free the resources owned by any no longer needed r-value. The resources left in the r-value won't be reused anyway, because the r-value is either a temporary object that will soon be destroyed or an object that will soon be (move-)? assigned a new value. The typical scenario where it would be helpful if the r-value is left without owning resources is a function that forwards some of its (r-value) argument to another function, but has no information about the forwarded arguments except that the other function requires these as arguments. All resources left in the r-value would be around at least until the function exits, and the might be a waste of resources. So it seems to be a good idea to advise any function that takes an r-value argument to try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to free the resources owned by its r-value argument).
This has been discussed before, but I agree that we should have a consensus on these issues. Maybe committee members have some guidelines for move semantics.
I found the following http://www.open-std.org/JTC1/sc22/WG21/docs/papers/2002/n1377.htm "The difference between a copy and a move is that a copy leaves the source unchanged. A move on the other hand leaves the source in a state defined differently for each type. The state of the source may be unchanged, or it may be radically different. The only requirement is that the object remain in a self consistent state (all internal invariants are still intact). From a client code point of view, choosing move instead of copy means that you don't care what happens to the state of the source." Both Peter Dimov and Howard Hinnant participated in the above thread. I had the impression that they still consider the above requirements sufficient. However, Howard Hinnant also stated in the above thread "Originally I preferred vector move assignment as just swap(). However I've recently become convinced that this is not the best definition. I now prefer the semantics of clear(); swap();." Even if the advice "Any function that takes an r-value argument should try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to try to free the resources owned by its r-value argument)." would be present in some important place, the semantics of 'clear(); swap();' for move assignment would still be questionable, because the memory owned by the vector is not freed. So what's my own opinion on this topic now? Trying to free resources owned by r-value arguments before returning is probably a good idea, because it is often what the programmer expects, it improves decoupling of concerns and reduces unexpected surprised when forwarding of r-value arguments. As a consequence, even so implementing the move-assignment operator as a pure swap looks temping at first sight, this is not the best definition. Regards, Thomas

Thomas Klimpel wrote:
Even if the advice
"Any function that takes an r-value argument should try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to try to free the resources owned by its r-value argument)."
would be present in some important place, the semantics of 'clear(); swap();' for move assignment would still be questionable, because the memory owned by the vector is not freed.
I tried to read a bit more about r-value references, and came to the conclusion that the above advice is not a good idea. Passing arguments by r-value reference is much closer related to passing arguments by l-value reference than to pass arguments by value. Especially, neither slicing nor destructor calls happen. The main difference seems to be that an r-value reference can also bind to a temporary, but is often a worse match than an l-value reference.
So what's my own opinion on this topic now? Trying to free resources owned by r-value arguments before returning is probably a good idea, because it is often what the programmer expects, it improves decoupling of concerns and reduces unexpected surprised when forwarding of r-value arguments.
Even so it looked like a good idea, it is not. It wouldn't work anyway, because nobody would abide to such a rule, and you wouldn't be able to ensure that you really free the resources for all possible control paths, because RAII would no longer be able to help you. But what's more important is that it doesn't make sense for the semantic of pass by r-value reference, because pass by r-value reference is too different from pass by values that trying to emulate the behavior of pass by value would make sense.
As a consequence, even so implementing the move-assignment operator as a pure swap looks temping at first sight, this is not the best definition.
This statement may still be true (only the "As a consequence" is not true). One reason is that std::auto_ptr is sometimes considered as an example for a class that has an assignment operator with move semantics. But the move-assignment operator of std::auto_ptr is not implemented as a pure swap, but frees the resources. As a consequence, most smart pointers will probably follow the precedence of std::auto_ptr, and free the resources in the move-assignment operator. So it might indeed be surprising if the resources would not be freed for a vector of smart pointers. Howard Hinnant wrote:
The difference might be important when the vector holds items whose destructors have side effects whose timing might be significant (e.g. vector<thread>).
LWG 675 (http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#675) is currently tracking this issue.
So what's my own opinion on this topic now? I don't know any more. I now have to impression that r-value references opened more questions than the proposals answered explicitly. The swap(T&, T&&) functions are examples of this. (I think they are simply a bad idea, but they may be voted into the standard nevertheless). The swap functions also raise the question about the type of the implicit "*this" argument of a member function. Compatibility with prior behavior seems to dictate that its type must be "T&&". But should there be a way to have member functions where the implicit type is T&? (Similar to const member function where "*this" has the type "const T&"). The swap function also raises interesting questions about slicing behavior, because the base class of a derived class might be swapped away, possibly violating some class invariant. So now I'm waiting for the next part of the article "http://cpp-next.com/archive/2009/08/want-speed-pass-by-value", because as Dave Abrahams wrote:
Hold your horses, people! All (well, much) will be revealed when we cover rvalue refs in the next installment. :-)
While at it, I wrote some tests to compare boost::move and c++0x rvalue refs behavior. I attached the file "rvalue.cpp", which only compiles in C++0x mode, the file "moveemulation.cpp", which also compiles without C++0x mode, and reproduces the behavior of rvalue.cpp when compiled in C++0x mode. I also attached the output of rvalue.cpp in C++0x mode ("rvalue.log"), the output of moveemulation.cpp in C++0x mode ("move0x.log"), and the output of moveemulation.cpp without C++0x mode ("moveemulation.log"). As intended, "diff rvalue.log move0x.log" reports that rvalue.log and move0x.log are identical. On the other hand "diff -u move0x.log moveemulation.log" reports: --- move0x.log 2009-09-06 23:55:49.015625000 +0200 +++ moveemulation.log 2009-09-06 23:55:39.671875000 +0200 @@ -1,7 +1,7 @@ base constructors: ++d_: 1 -bmc +bcc ++b_: 1 base(), b, cb: @@ -38,6 +38,6 @@ slicing assignments: bca -bma +bca ++d_: 1 bma It turns out that the differences are exactly the slicing move constructors and move assignments. In C++0x mode, the slicing move constructors and move assignment operators are selected, but without C++0x mode the normal copy constructors and copy assignment operators are used. But I have to admit that I tested only with gcc 4.3.2, perhaps other compilers will produce different results. I was quite surprised how successful Boost.Move was able to emulate the C++0x rvalue references. Regards, Thomas

on Mon Aug 24 2009, Thomas Klimpel <Thomas.Klimpel-AT-synopsys.com> wrote:
Ion Gaztañaga wrote:
Thomas Klimpel escribió:
So my position is that the user better be prepared to expect that "a = boost::move(b); " can be implemented as "a.swap(b)", because move assignment is about efficiency, and a.swap(b) is often the most efficient implementation of move assignment.
Some additional discussion can be found in this thread:
http://lists.boost.org/Archives/boost/2007/04/119872.php
I was also against clear(), but somehow people convinced me and I added clear() to my classes ;-)
I have read that thread now. I didn't have the impression that this thread really reached a conclusion. I like the idea of Howard Hinnant to examine the (generic) algorithms that work with move semantics to determine how move-construction and move-assignment should behave. However, the example he gives why he prefers the semantics of clear(); swap(); is not a (generic) algorithm but an example with concrete types:
Howard Hinnant wrote:
Now consider vector<thread>: vector<thread> v1; ... vector<thread> v2; ... v1 = std::move(v2);
After the move assign, I think it best that whatever threads v1 happened to previously refer to, are now busy canceling themselves, instead of continuing to run under v2's ownership. If you want the latter, you've got swap.
If the user wants to ensure that the threads get canceled, he can call "v2.clear();" himself.
I disagree. It's OK if transforming "x" into "move(x)" changes semantics with respect to x. It isn't OK if it changes semantics with respect to objects other than x. In particular, y = x; ===> y = move(x); should not change anything about what happens with y. That's just too capricious.
So the question for me would be whether there are (generic) algorithms (because they can't call x.clear() themselves) that will use more resources if the move-assignment doesn't call x.clear() for them. After all, those (generic) algorithms are the real clients that will call std::move (x) and will have to deal with non-temporary moved from objects.
Here's another example: // C++03 template <class Pair, class T> T& replace2nd(Pair& c, T const& x) { return c.second = x; } pair<thread,thread> threads; ... long_running_operation(replace2nd(threads, new_thread())); // (**) OK, let's move-enable replace2nd: // addtional overload template <class Pair, class T> T& replace2nd(Pair& c, T&& x) { return c.second = move(x); } can't clear there; we don't know that T has a clear(). Now we've just changed the semantics of the line marked (**), since the old thread won't be canceled until the long-running operation is complete. Replace "thread" with "lock" and now you can easily deadlock, because you've messed with tghe locking/unlocking order. I'm pretty sure we can massage this example easily to eliminate even the appearance of move() in the code. Oh, sure: // C++03 template <class Pair, class F> T& replace2nd(Pair& c, F const& f) { return c.second = f(); // f might return an rvalue }
The programmer has not required any value swapping just a "transfer" and I think the programmer expects all previous v1 values should have been destroyed.
To be honest, it is exactly because of this argument why I don't like the clear() in the assignment operator of vector. The words "The only requirement is that the object remain in a self consistent state (all internal invariants are still intact)." don't forbid to implement move-assignment as swap,
The other requirement is that user-observable side-effects of assignment from an lvalue must be preserved. If you own a T that's an implementation detail, you can swap it away and nobody's the wiser. If, as in vector<T>, pair<T>, shared_ptr<T>, etc., you have a T that the user can observe, you need to make sure it is destroyed.
so if the programmer expects something incompatible with this implementation, he is relying on undefined behavior.
Huh? How do you reach that conclusion?
On the other hand, there might be other good reasons to call clear() in the assignment operator, or more generally try to free the resources owned by any no longer needed r-value. The resources left in the r-value won't be reused anyway, because the r-value is either a ^--- usually temporary object that will soon be destroyed or an object that will soon be (move-)? assigned a new value.
Exactly. The capacity of a vector can be valuable when assigning. template <class It, class T> void push_front_like(It start, It finish, T const& x) { while (--finish != start) { It prev = finish; --prev; *finish = std::move(*prev); } // If there's enough capacity left in *start, we avoid a // deallocation and an allocation *start = x; }
The typical scenario where it would be helpful if the r-value is left without owning resources is a function that forwards some of its (r-value) argument to another function, but has no information about the forwarded arguments except that the other function requires these as arguments. All resources left in the r-value would be around at least until the function exits, and the might be a waste of resources. So it seems to be a good idea to advise any function that takes an r-value argument to try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to free the resources owned by its r-value argument).
You can't do any of that if you don't know anything special about the type of the argument.
This has been discussed before, but I agree that we should have a consensus on these issues. Maybe committee members have some guidelines for move semantics.
I found the following http://www.open-std.org/JTC1/sc22/WG21/docs/papers/2002/n1377.htm
"The difference between a copy and a move is that a copy leaves the source unchanged. A move on the other hand leaves the source in a state defined differently for each type. The state of the source may be unchanged, or it may be radically different. The only requirement is that the object remain in a self consistent state (all internal invariants are still intact). From a client code point of view, choosing move instead of copy means that you don't care what happens to the state of the source."
Both Peter Dimov and Howard Hinnant participated in the above thread. I had the impression that they still consider the above requirements sufficient. However, Howard Hinnant also stated in the above thread "Originally I preferred vector move assignment as just swap(). However I've recently become convinced that this is not the best definition. I now prefer the semantics of clear(); swap();."
Exactly. I think Howard just didn't know how to phrase the requirement on user-visible side-effects. Credit where due: he's the one who explained this to me.
Even if the advice
"Any function that takes an r-value argument should try to free the resources owned by its r-value argument before returning, either by explicitly freeing the resources or by forwarding the r-value to another function (thereby forwarding the responsibility to try to free the resources owned by its r-value argument)."
would be present in some important place, the semantics of 'clear(); swap();' for move assignment would still be questionable, because the memory owned by the vector is not freed.
I disagree. The capacity of a vector is not part of its value. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Sep 7, 2009, at 7:53 AM, David Abrahams wrote:
The programmer has not required any value swapping just a "transfer" and I think the programmer expects all previous v1 values should have been destroyed.
To be honest, it is exactly because of this argument why I don't like the clear() in the assignment operator of vector. The words "The only requirement is that the object remain in a self consistent state (all internal invariants are still intact)." don't forbid to implement move-assignment as swap,
The other requirement is that user-observable side-effects of assignment from an lvalue must be preserved. If you own a T that's an implementation detail, you can swap it away and nobody's the wiser. If, as in vector<T>, pair<T>, shared_ptr<T>, etc., you have a T that the user can observe, you need to make sure it is destroyed.
In addition to the example Dave provided, here is an example which you can compile with a C++98 compiler (after adding boost and changing the namespace of std::shared_ptr): #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <memory> #include <cassert> int main() { typedef std::shared_ptr<std::ofstream> Resource; std::vector<Resource> x; const int N = 10; for (int i = 1; i <= N; ++i) { std::ostringstream filename; filename << "file" << i << ".dat"; x.push_back(Resource(new std::ofstream(filename.str().c_str ()))); *x.back() << "This is " << filename.str() << '\n'; } std::vector<Resource> y(1, Resource(new std::ofstream ("last_file.dat"))); x = y; for (int i = 1; i <= N; ++i) { std::ostringstream filename; filename << "file" << i << ".dat"; std::ifstream infile(filename.str().c_str()); std::string line; getline(infile, line); std::cout << line << '\n'; } } All this does is create a vector<shared_ptr<ofstream>>, fill it up with some files, write to those files, and then copy-assign the vector with another vector. Then it opens the files that were just copy- assigned on top of and prints out the contents. For me this program prints out: This is file1.dat This is file2.dat This is file3.dat This is file4.dat This is file5.dat This is file6.dat This is file7.dat This is file8.dat This is file9.dat This is file10.dat Now, notice that after: x = y; the value of 'y' is never used again. Indeed, after this assignment this program doesn't care about the value of 'y'. In C++0x I ought to be able to optimize this program with: x = std::move(y); and have the behavior of the program remain unchanged. Using an experimental C++0x compiler and library (you might can do this experiment g++-4.4, I'm not sure) I make this substitution and indeed, I get the same output (remove all of the *.dat files prior to each run). This experimental library has: template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; } However, if I remove the "clear()" and rerun the program (remember to remove the *.dat files first), then it outputs: (a bunch of newlines) Without the "clear()" my "optimization" has altered the visible behavior of this program (Bad!(tm)). Finally, std::remove_if is a generic algorithm that will move assign from a value, and not destruct that moved-from value nor assign a new value to that moved-from value. It simply leaves it alone for the client to deal with. If given a copy constructible type, std::remove_if should have the same behavior (except for performance) whether it internally uses copy assignment or move assignment. -Howard

Howard Hinnant wrote:
Using an experimental C++0x compiler and library (you might can do this experiment g++-4.4, I'm not sure) I make this substitution and indeed, I get the same output (remove all of the *.dat files prior to each run). This experimental library has:
template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; }
Perhaps a dumb question, but isn't the above incorrect under self-assigment (x = move(x))? Is there a "standard way" or "recommended guideline" for handling self-assignment in the move assignment operator, or is this a nonissue? - Jeff

On 7 Sep 2009, at 20:08, Jeffrey Hellrung wrote:
Howard Hinnant wrote:
Using an experimental C++0x compiler and library (you might can do this experiment g++-4.4, I'm not sure) I make this substitution and indeed, I get the same output (remove all of the *.dat files prior to each run). This experimental library has: template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; }
Perhaps a dumb question, but isn't the above incorrect under self- assigment (x = move(x))? Is there a "standard way" or "recommended guideline" for handling self-assignment in the move assignment operator, or is this a nonissue?
I'm actually not 100% positive what the intended semantics of x = move (x) are, but clearly as a QOI issue it should behave. Most people put: if(&__x != this) or something similar in normal assignment operators, adding it also to all move operators seems very sensible. Chris

On Sep 7, 2009, at 3:26 PM, Christopher Jefferson wrote:
On 7 Sep 2009, at 20:08, Jeffrey Hellrung wrote:
Howard Hinnant wrote:
Using an experimental C++0x compiler and library (you might can do this experiment g++-4.4, I'm not sure) I make this substitution and indeed, I get the same output (remove all of the *.dat files prior to each run). This experimental library has: template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; }
Perhaps a dumb question, but isn't the above incorrect under self- assigment (x = move(x))? Is there a "standard way" or "recommended guideline" for handling self-assignment in the move assignment operator, or is this a nonissue?
I'm actually not 100% positive what the intended semantics of x = move(x) are, but clearly as a QOI issue it should behave.
Most people put:
if(&__x != this)
or something similar in normal assignment operators, adding it also to all move operators seems very sensible.
My hope has been to make x = move(x) undefined behavior. Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level). The philosophy has been that code should be able to assume that if given an rvalue, then it /really/ is an rvalue. And if the client lied by moving an lvalue, then it is up to the client to make sure that lie is really safe. And if the rhs /really is/ an rvalue, then self assignment is impossible. After all, one of the primary motivations for move semantics is performance. So I'd really like it to be as fast as possible. The slower it is, the less important it is. The draft standard could probably use a statement to that effect. Currently the closest thing in there with regards to this issue is [container.requirements.general]/12:
An object bound to an rvalue reference parameter of a member function of a container shall not be an element of that container; no diagnostic required.
-Howard

Howard Hinnant wrote:
On Sep 7, 2009, at 3:26 PM, Christopher Jefferson wrote:
On 7 Sep 2009, at 20:08, Jeffrey Hellrung wrote:
Howard Hinnant wrote:
Using an experimental C++0x compiler and library (you might can do this experiment g++-4.4, I'm not sure) I make this substitution and indeed, I get the same output (remove all of the *.dat files prior to each run). This experimental library has: template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; }
Perhaps a dumb question, but isn't the above incorrect under self- assigment (x = move(x))? Is there a "standard way" or "recommended guideline" for handling self-assignment in the move assignment operator, or is this a nonissue?
It's incorrect under self-assignment. The correct version is T& operator=( T&& x ) { T( move(x) ).swap( *this ); return *this; } or its inlined equivalent.
I'm actually not 100% positive what the intended semantics of x = move(x) are, but clearly as a QOI issue it should behave.
Most people put:
if(&__x != this)
or something similar in normal assignment operators, adding it also to all move operators seems very sensible.
This is not enough in general. clear() may end up indirectly destroying the contents of x, even when &x != this. (Although... now that I think of it... this may be a problem with the vector copy assignment as well.)
My hope has been to make x = move(x) undefined behavior.
I find this a bit hard to justify.
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?

On Sep 7, 2009, at 4:44 PM, Peter Dimov wrote:
My hope has been to make x = move(x) undefined behavior.
I find this a bit hard to justify.
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?
It has been part of the design for the past 7 years. From N1377 (2002-09-10):
Move semantics will automatically come into play when given rvalue arguments. This is perfectly safe because moving resources from an rvalue can not be noticed by the rest of the program (<em>nobody else has a reference to the rvalue in order to detect a difference</ em>).
Here it is again in N1690 (2004-09-07):
When the argument is an rvalue, the author of the class knows that he has a unique reference to the argument.
The overload taking an rvalue can modify the argument in whatever invariant preserving way it pleases and be guaranteed that the rest of the program will not notice!
I'm currently thinking that we need a Chapter 17 statement along the lines of: The C++ standard library may assume that an rvalue reference represents a unique reference to the bound object. This statement would have been more difficult to write the standardeze for prior to N2831 (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2831.html ). This would make [container.requirements.general]/12 redundant, and that paragraph could thus be removed. This would also mean that for every class type defined by the standard library with a move assignment operator: C& C::operator=(C&& rhs); This signature may assume that rhs represents the only reference to the object bound to rhs. After all if the library can't assume (for example) that in vector::push_back(T&& t); t is a unique reference to some object, then it can't very well destructively modify that object while appending it to the vector. -Howard

On 7 Sep 2009, at 21:44, Peter Dimov wrote:
I'm actually not 100% positive what the intended semantics of x = move(x) are, but clearly as a QOI issue it should behave.
Most people put:
if(&__x != this)
or something similar in normal assignment operators, adding it also to all move operators seems very sensible.
This is not enough in general. clear() may end up indirectly destroying the contents of x, even when &x != this. (Although... now that I think of it... this may be a problem with the vector copy assignment as well.)
My hope has been to make x = move(x) undefined behavior.
I find this a bit hard to justify.
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?
Actually thinking about it, I wonder if when this occurs it is a mistake -- should instead a debug mode assert if &x == &y? x = move(x) actually seems like it would be a logic error. Certainly having now implemented a reasonable amount of rvalue code, including a standard library implementation, I can't think of where it would arise without an error. move(x) implies we do not care about the value of x, but then the assignment means that we do care. Certainly I'd want to see an assert in debug mode, to catch this, as I imagine errors it causes could be quite subtle. Chris

On Sep 7, 2009, at 5:20 PM, Christopher Jefferson wrote:
On 7 Sep 2009, at 21:44, Peter Dimov wrote:
I'm actually not 100% positive what the intended semantics of x = move(x) are, but clearly as a QOI issue it should behave.
Most people put:
if(&__x != this)
or something similar in normal assignment operators, adding it also to all move operators seems very sensible.
This is not enough in general. clear() may end up indirectly destroying the contents of x, even when &x != this. (Although... now that I think of it... this may be a problem with the vector copy assignment as well.)
My hope has been to make x = move(x) undefined behavior.
I find this a bit hard to justify.
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?
Actually thinking about it, I wonder if when this occurs it is a mistake -- should instead a debug mode assert if &x == &y?
x = move(x) actually seems like it would be a logic error. Certainly having now implemented a reasonable amount of rvalue code, including a standard library implementation, I can't think of where it would arise without an error. move(x) implies we do not care about the value of x, but then the assignment means that we do care.
Certainly I'd want to see an assert in debug mode, to catch this, as I imagine errors it causes could be quite subtle.
<nod> Along the same line of thought a debug mode vector/deque::insert (p, T&& t) could detect/assert if &t is within the range of those objects that need to be moved to make room for t. -Howard

I wrote:
T& operator=( T&& x ) { T( move(x) ).swap( *this ); return *this; }
This is the generic version, but for vector, one might also consider the equivalent of T& operator=( T&& x ) { T tmp( move(x) ); clear(); swap( x ); swap( tmp ); return *this; } to avoid deallocating the current capacity of *this.

on Mon Sep 07 2009, "Peter Dimov" <pdimov-AT-pdimov.com> wrote:
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?
Most of the time, yeah. Definitely, in most of the generic algorithms. But I'm not sure whether it's true enough of the time. Consider our new generic std::swap: template <class T> void swap(T& x, T& y) { T z = std::move(x); x = std::move(y); // <== uh oh? y = std::move(z); } Do we think swap(x,y) is also an error when x == y? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On 7 Sep 2009, at 23:03, David Abrahams wrote:
on Mon Sep 07 2009, "Peter Dimov" <pdimov-AT-pdimov.com> wrote:
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?
Most of the time, yeah. Definitely, in most of the generic algorithms. But I'm not sure whether it's true enough of the time. Consider our new generic std::swap:
template <class T> void swap(T& x, T& y) { T z = std::move(x); x = std::move(y); // <== uh oh? y = std::move(z); }
Do we think swap(x,y) is also an error when x == y?
Last time I checked (which was a couple of years ago) STLport did a self-swap when std::reverse was given an odd-length list, and wouldn't accept a patch to change the behaviour. Chris

On Sep 7, 2009, at 6:03 PM, David Abrahams wrote:
on Mon Sep 07 2009, "Peter Dimov" <pdimov-AT-pdimov.com> wrote:
Rationale: The move assignment operator should be fast enough that an extra if- test may adversely impact performance (above the noise level).
The problem with making it UB is that y = move(x) now needs to be guarded with if( &x != &y ) everywhere in client code. Sometimes the user will know that x is not y, but is this really the case most of the time?
Most of the time, yeah. Definitely, in most of the generic algorithms. But I'm not sure whether it's true enough of the time. Consider our new generic std::swap:
template <class T> void swap(T& x, T& y) { T z = std::move(x); x = std::move(y); // <== uh oh? y = std::move(z); }
Do we think swap(x,y) is also an error when x == y?
This is a decent argument to not assert in self-move-assignment. But this is also almost certainly harmless: #include <algorithm> #include <iostream> class A { int data_; public: explicit A(int data = 0) : data_(data) {} A(A&& a) : data_(a.data_) {a.data_ = 0;} A& operator=(A&& a) {data_ = a.data_; a.data_ = 0; return *this;} friend std::ostream& operator<<(std::ostream& os, const A& a) { return os << a.data_; } }; int main() { A a(5); std::cout << "Before swap a = " << a << '\n'; std::swap(a, a); std::cout << "After swap a = " << a << '\n'; } Before swap a = 5 After swap a = 5 But if I caught my own code doing a self-swap, yeah, I would treat it as a bug and correct it. For example, every time I've written reverse (which does nothing but swap x and y while x and y move closer to each other in the sequence), I'm careful to break out of the loop before &x == &y. -Howard

on Mon Sep 07 2009, Howard Hinnant <howard.hinnant-AT-gmail.com> wrote:
Do we think swap(x,y) is also an error when x == y?
This is a decent argument to not assert in self-move-assignment.
And not to call it undefined behavior. self-std:swapping was allowed in C++03 and I don't think we can break that.
But this is also almost certainly harmless:
#include <algorithm> #include <iostream>
class A { int data_; public: explicit A(int data = 0) : data_(data) {} A(A&& a) : data_(a.data_) {a.data_ = 0;} A& operator=(A&& a) {data_ = a.data_; a.data_ = 0; return *this;} friend std::ostream& operator<<(std::ostream& os, const A& a) { return os << a.data_; } };
int main() { A a(5); std::cout << "Before swap a = " << a << '\n'; std::swap(a, a); std::cout << "After swap a = " << a << '\n'; }
Before swap a = 5 After swap a = 5
Yes, it's harmless, but I don't see how it's a "but." I must be missing your point, because that example seems to support my argument.
But if I caught my own code doing a self-swap, yeah, I would treat it as a bug and correct it. For example, every time I've written reverse (which does nothing but swap x and y while x and y move closer to each other in the sequence), I'm careful to break out of the loop before &x == &y.
Yes, but if it's reverse's job to avoid that, then it's also swap's job (both are algorithms, neh?) So should we put a self-swap test there? -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Sep 7, 2009, at 6:50 PM, David Abrahams wrote:
on Mon Sep 07 2009, Howard Hinnant <howard.hinnant-AT-gmail.com> wrote:
Do we think swap(x,y) is also an error when x == y?
This is a decent argument to not assert in self-move-assignment.
And not to call it undefined behavior. self-std:swapping was allowed in C++03 and I don't think we can break that.
But this is also almost certainly harmless:
#include <algorithm> #include <iostream>
class A { int data_; public: explicit A(int data = 0) : data_(data) {} A(A&& a) : data_(a.data_) {a.data_ = 0;} A& operator=(A&& a) {data_ = a.data_; a.data_ = 0; return *this;} friend std::ostream& operator<<(std::ostream& os, const A& a) { return os << a.data_; } };
int main() { A a(5); std::cout << "Before swap a = " << a << '\n'; std::swap(a, a); std::cout << "After swap a = " << a << '\n'; }
Before swap a = 5 After swap a = 5
Yes, it's harmless, but I don't see how it's a "but." I must be missing your point, because that example seems to support my argument.
But if I caught my own code doing a self-swap, yeah, I would treat it as a bug and correct it. For example, every time I've written reverse (which does nothing but swap x and y while x and y move closer to each other in the sequence), I'm careful to break out of the loop before &x == &y.
Yes, but if it's reverse's job to avoid that, then it's also swap's job (both are algorithms, neh?) So should we put a self-swap test there?
No, I don't think so. std::swap will only do a self-move-assignment using a moved-from object. Of course vector will never use this definition. But if it did, then: template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; } would work just fine. clear() is a no-op, and then you swap an empty capacity vector with itself. My point is that I don't want to be told I need to put a if(this != &x) in my move assignment operator. It is a useless performance penalty. Everyone should not have to pay (in terms of lost performance) for someone's bad code just to make sure self-move- assignment does a no-op instead of crashes. If you want to cast an lvalue to an rvalue, that's fine. Just make sure your code logic is such that such a cast is safe. I believe std::swap's use of move assignment is safe because by the time it move-assigns, if it is self referencing, all of your resources have already been moved to a local temp. -Howard

on Mon Sep 07 2009, Howard Hinnant <howard.hinnant-AT-gmail.com> wrote:
std::swap will only do a self-move-assignment using a moved-from object.
You mean the target will be a moved-from object. OK, so?
Of course vector will never use this definition. But if it did, then:
template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; }
would work just fine. clear() is a no-op, and then you swap an empty capacity vector with itself.
My point is that I don't want to be told I need to put a if(this != &x) in my move assignment operator. It is a useless performance penalty. Everyone should not have to pay (in terms of lost performance) for someone's bad code just to make sure self-move- assignment does a no-op instead of crashes.
And the reason this argument doesn't also apply to copy assignment is...?
If you want to cast an lvalue to an rvalue, that's fine. Just make sure your code logic is such that such a cast is safe. I believe std::swap's use of move assignment is safe because by the time it move-assigns, if it is self referencing, all of your resources have already been moved to a local temp.
Then what is the coding guideline, exactly? "Make sure it's safe" doesn't qualify. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Sep 7, 2009, at 8:37 PM, David Abrahams wrote:
on Mon Sep 07 2009, Howard Hinnant <howard.hinnant-AT-gmail.com> wrote:
std::swap will only do a self-move-assignment using a moved-from object.
You mean the target will be a moved-from object.
<nod>
OK, so?
So the target is resource-less. There are no pre-existing resources the target must get rid of prior to the move-assignment. That means you're not going to delete yourself, even if the move assignment is self-referencing.
Of course vector will never use this definition. But if it did, then:
template <class _Tp, class _Allocator> inline vector<_Tp, _Allocator>& vector<_Tp, _Allocator>::operator=(vector&& __x) { clear(); swap(__x); return *this; }
would work just fine. clear() is a no-op, and then you swap an empty capacity vector with itself.
My point is that I don't want to be told I need to put a if(this != &x) in my move assignment operator. It is a useless performance penalty. Everyone should not have to pay (in terms of lost performance) for someone's bad code just to make sure self-move- assignment does a no-op instead of crashes.
And the reason this argument doesn't also apply to copy assignment is...?
swap doesn't use copy assignment. ;-) More seriously because:
I'm currently thinking that we need a Chapter 17 statement along the lines of:
The C++ standard library may assume that an rvalue reference represents a unique reference to the bound object.
This simple statement removes sometimes-expensive error checking that is vanishingly-rarely useful. One can not self-assign a true (non- casted) rvalue. Move semantics only works because the code with the reference to the rvalue can assume that it has the *only* reference to that rvalue. (this is sort of like C99's restrict in reverse)
If you want to cast an lvalue to an rvalue, that's fine. Just make sure your code logic is such that such a cast is safe. I believe std::swap's use of move assignment is safe because by the time it move-assigns, if it is self referencing, all of your resources have already been moved to a local temp.
Then what is the coding guideline, exactly? "Make sure it's safe" doesn't qualify.
Agreed. A good guideline might go along the lines of: if you move assign, the target should generally be moved from first. If it is not, ensure that target and source do not refer to the same object. This is not as hard as it sounds: Every single in-place-sequence- permuting algorithm in <algorithm>, including swap, sort, rotate, random_shuffle, partition, nth_element, etc. works by either swapping, or by move-constructing an element out to a local temp, and then move assigning into the "hole" (and then move assigning into the new "hole"). I.e. these algorithms always move assign into a moved-from element. This behavior also includes the container member algorithms for vector/deque insert. An exception to the above statement is those algorithms which "shorten" the sequence: remove and remove_if, and the container member functions erase (for vector and deque). Here the initial move assignment is into a desired-removed but not moved-from element. This algorithm will typically result in the trailing "removed" elements all being moved-from. In these algorithms, there is no chance of move assignment being self referencing (except for a coding error on the part of the author of remove/erase, etc.). -Howard

I know that this discussion is old, and that a review manager for the review is still missing. However, since the article series at "http://cpp-next.com" has now covered quite some ground, I somehow guess that a review manager will be found sooner or later. (More precisely, I guess that the article series on value semantics, moving and all that will soon be finished, and that then a review manager will appear somehow...) And because the article series has covered quite some ground now, it's also easier for me to keep up with the discussion. David Abrahams wrote:
Thomas Klimpel wrote:
If the user wants to ensure that the threads get canceled, he can call "v2.clear();" himself.
I disagree. It's OK if transforming "x" into "move(x)" changes semantics with respect to x. It isn't OK if it changes semantics with respect to objects other than x. In particular,
y = x; ===> y = move(x);
should not change anything about what happens with y. That's just too capricious.
I'm not sure about this. An alternative would be to transform "x" into "X(move(x))" (move constructor call). At least this solution would be more general than the suggestion to call "v2.clear();". Yes, I know that the move constructor "X(X&&x)" may not exist for a general "x". But the case "X(move(x))" is still quite common, especially most "return std::move(x);" statements will probably happen in functions that return a value, so the move constructor call will happen implicitly.
So the question for me would be whether there are (generic) algorithms (because they can't call x.clear() themselves) that will use more resources if the move-assignment doesn't call x.clear() for them. After all, those (generic) algorithms are the real clients that will call std::move (x) and will have to deal with non-temporary moved from objects.
Here's another example:
// C++03 template <class Pair, class T> T& replace2nd(Pair& c, T const& x) { return c.second = x; }
pair<thread,thread> threads; ...
long_running_operation(replace2nd(threads, new_thread())); // (**)
OK, let's move-enable replace2nd:
// addtional overload template <class Pair, class T> T& replace2nd(Pair& c, T&& x) { return c.second = move(x); }
can't clear there; we don't know that T has a clear().
Now we've just changed the semantics of the line marked (**), since the old thread won't be canceled until the long-running operation is complete.
Interesting. I have to admit that I assumed the temporary objects for the arguments to replace2nd would be destroyed before the return value of replace2nd gets used, but this assumption was probably wrong. I guess the C++ standard just says that they get destroyed after the entire expression is evaluated. Just a simple question for my education: Does the C++ standard explicitly guarantees that the temporary objects don't get destroyed before the entire expression is evaluated, or is the exact moment of the destructor calls simply unspecified? The appearance of move in the template function doesn't invalidate this counter example, because the generic code can't know whether T has a move-constructor that could be called to ensure a destructor call.
Replace "thread" with "lock" and now you can easily deadlock, because you've messed with tghe locking/unlocking order. I'm pretty sure we can massage this example easily to eliminate even the appearance of move() in the code. Oh, sure:
// C++03 template <class Pair, class F> T& replace2nd(Pair& c, F const& f) { return c.second = f(); // f might return an rvalue }
If "f" just returns an rvalue (not an rvalue-reference), it will be destroyed before replace2nd returns, and nothing will go wrong. OK, so you suggest that "f" might return an rvalue-reference? This might actually be an extremely good point, even if the C++ language would be changed to invalidate the first counter example. But I wonder a bit where "f" wants to store the object corresponding to the rvalue-reference, and what "f" plans to do when it gets called the next time. I somehow get the impression that "f" won't be very thread safe.
The words "..." don't forbid to implement move-assignment as swap so if the programmer expects something incompatible with this implementation, he is relying on undefined behavior.
Huh? How do you reach that conclusion?
If it is not undefined behavior, it should be possible to point out where this behavior is defined. Thanks to your article series, I now know at least one place where it is defined: http://cpp-next.com/archive/2009/09/your-next-assignment/ So what is the conclusion? Do I really still pretend to need a convincing counter example? I certainly had hoped for a different kind of counter example, more something on the algorithmic side (something like move_iterator used with some existing standard algorithm running into trouble when move-assignment is implemented as swap). The first counter example is really disappointing from my point of view. It's certainly not good style to call a counter example a language defect, but if "replace2nd" had the signature template <class Pair, class T> T& replace2nd(Pair& c, T x) and "copy elision" kicked in, the destructor call would happen before the return value of replace2nd gets used. So even changing the signature to template <class Pair, class T> T& replace2nd(Pair& c, const T& x) could have surprising side effects by changing the exact moment when the destructor of the object returned by new_thread() gets called. The second counter example was certainly more interesting, because it shows that I would have to strongly discourage functions from returning rvalue-references, if I want to implement move-assignment as swap. Now I'm not sure whether returning rvalue-references is a good idea, but I have to admit that generally discouraging it seems to be strange. So I'm left with the impression that there might be quite some good reasons not to implement move-assignment as swap, but no really conclusive counter examples against it. On the other hand, as long as the absence of conclusive counter examples can't be "proved", ruling that move-assignment may generally be implemented as swap will be risky or worse. Regards, Thomas

on Thu Oct 15 2009, Thomas Klimpel <Thomas.Klimpel-AT-synopsys.com> wrote:
I know that this discussion is old, and that a review manager for the review is still missing.
I'll manage the review if nobody else is stepping up.
However, since the article series at "http://cpp-next.com" has now covered quite some ground, I somehow guess that a review manager will be found sooner or later. (More precisely, I guess that the article series on value semantics, moving and all that will soon be finished, and that then a review manager will appear somehow...)
Not sure what C++Next has to do with it, but...
It's OK if transforming "x" into "move(x)" changes semantics with respect to x. It isn't OK if it changes semantics with respect to objects other than x. In particular,
y = x; ===> y = move(x);
should not change anything about what happens with y. That's just too capricious.
I'm not sure about this. An alternative would be to transform "x" into "X(move(x))" (move constructor call). At least this solution would be more general than the suggestion to call "v2.clear();".
As http://cpp-next.com/archive/2009/09/your-next-assignment/ points out, generality isn't the point of move---optimization is--- and if you want generality, you can go with X& operator=(X&& rhs) { X(std::move(rhs)) .swap(*this); return *this; }
Here's another example:
// C++03 template <class Pair, class T> T& replace2nd(Pair& c, T const& x) { return c.second = x; }
pair<thread,thread> threads; ...
long_running_operation(replace2nd(threads, new_thread())); // (**)
OK, let's move-enable replace2nd:
// addtional overload template <class Pair, class T> T& replace2nd(Pair& c, T&& x) { return c.second = move(x); }
can't clear there; we don't know that T has a clear().
Now we've just changed the semantics of the line marked (**), since the old thread won't be canceled until the long-running operation is complete.
Interesting. I have to admit that I assumed the temporary objects for the arguments to replace2nd would be destroyed before the return value of replace2nd gets used, but this assumption was probably wrong. I guess the C++ standard just says that they get destroyed after the entire expression is evaluated.
Yes
Just a simple question for my education: Does the C++ standard explicitly guarantees that the temporary objects don't get destroyed before the entire expression is evaluated,
Yes
or is the exact moment of the destructor calls simply unspecified?
No <snip>
So I'm left with the impression that there might be quite some good reasons not to implement move-assignment as swap, but no really conclusive counter examples against it. On the other hand, as long as the absence of conclusive counter examples can't be "proved", ruling that move-assignment may generally be implemented as swap will be risky or worse.
I guess we're in agreement, then :-) -- Dave Abrahams Meet me at BoostCon: http://www.boostcon.com BoostPro Computing http://www.boostpro.com

On 16 Oct 2009, at 02:10, Thomas Klimpel wrote:
So I'm left with the impression that there might be quite some good reasons not to implement move-assignment as swap, but no really conclusive counter examples against it. On the other hand, as long as the absence of conclusive counter examples can't be "proved", ruling that move-assignment may generally be implemented as swap will be risky or worse.
Just as one point of information, which I've previously seen used in favour of using swap as move. While it is true that a move which doesn't swap on (for example) vector<T> is O(n) rather than O(1), due to the need to destruct all the elements, I found in experiments that sorting a range of vector<T> is actually very slightly faster when move-assignment is NOT implemented as swap, so performance isn't even really an argument in it's favour. Chris

On Oct 17, 2009, at 4:39 AM, Christopher Jefferson wrote:
On 16 Oct 2009, at 02:10, Thomas Klimpel wrote:
So I'm left with the impression that there might be quite some good reasons not to implement move-assignment as swap, but no really conclusive counter examples against it. On the other hand, as long as the absence of conclusive counter examples can't be "proved", ruling that move-assignment may generally be implemented as swap will be risky or worse.
Just as one point of information, which I've previously seen used in favour of using swap as move.
While it is true that a move which doesn't swap on (for example) vector<T> is O(n) rather than O(1), due to the need to destruct all the elements, I found in experiments that sorting a range of vector<T> is actually very slightly faster when move-assignment is NOT implemented as swap, so performance isn't even really an argument in it's favour.
There's a theoretical foundation behind this empirical observation. sort is a "resource preserving" permutation (as are many, but not all, algorithms in <algorithm>). I.e. things just get rearranged. Nothing need be constructed or destructed, though such constructions/ destructions may happen as implementation details. swap is the simplest "resource preserving" permutation algorithm, and for the purposes of this argument, is no different from sort: template <class T> void swap(T& x, T& y) { T t(std::move(x)); x = std::move(y); y = std::move(t); } The construction of t will put x into a resource-less state. The move assignment of x is <em>into an object with a resource-less state</ em>. I.e. here it makes no difference if T's move assignment operator is implemented with or without clear(). There is nothing to clear(). This move assignment will put y into a resource-less state. In the last move assignment, it is again into an object with a resource-less state. There is nothing to clear() out of y by the time it is move- assigned into. This pattern: if you move assign into something, it is already resource-less; is just as true for sort as it is for swap. And for unique. And for stable_partition. And for stable_sort. And for inplace_merge. Etc. Thus the cost for clearing the lhs within a move-assignment prior to the transfer of resources is minimal. It is usually a no-op. So why do it? What's the benefit even if the cost is small? Consider std::remove: This is one of the few algorithms in <algorithm> which is not a "resource preserving" permutation. Consider remove({a, b, c, d, e}, c). This will result in the sequence {a, b, d, e, e'}. To achieve this result, the following move assignments will take place: c = move(d); d = move(e); The first move assignment is not into a resource-less c. If move assignment into c doesn't "clear", when do those resources go away? For most resources (such as memory), it doesn't really matter when. The resources will still be owned if move-assignment doesn't aggressively destroy them (probably by e'). But for some resources (threads, file handles, mutexes), it will matter if they live too long. The client calling std::remove may or may not destruct e' "soon". But the client asked std::remove to remove c (and presumably all of c's resources). I do not think it would be a good idea for std::remove to also say: Oh, and you better destruct e' too just to make sure c is really gone. This is the benefit of move assignment "clearing" potentially significant resources aggressively. I believe this benefit outweighs the cost. -Howard

Howard Hinnant wrote:
So why do it? What's the benefit even if the cost is small?
Consider std::remove: This is one of the few algorithms in <algorithm> which is not a "resource preserving" permutation. Consider remove({a, b, c, d, e}, c). This will result in the sequence {a, b, d, e, e'}. To achieve this result, the following move assignments will take place:
c = move(d); d = move(e);
The first move assignment is not into a resource-less c. If move assignment into c doesn't "clear", when do those resources go away? For most resources (such as memory), it doesn't really matter when. The resources will still be owned if move-assignment doesn't aggressively destroy them (probably by e').
But for some resources (threads, file handles, mutexes), it will matter if they live too long. The client calling std::remove may or may not destruct e' "soon". But the client asked std::remove to remove c (and presumably all of c's resources). I do not think it would be a good idea for std::remove to also say: Oh, and you better destruct e' too just to make sure c is really gone.
I was initially looking for an example of this sort to "prove" that "clear" is required, but came to realize that I probably won't find one. (The examples Dave gave me are sufficient to "prove" that "clear" is required, but they are related to subtleties of the C++ language and not inherent in moving itself.) But why do I think that this example doesn't "prove" that "clear" is required? Consider "remove({a, b, c, d, c}, c)" instead of "remove({a, b, c, d, e}, c)". This will result in the sequence {a, b, d, d', c}. So the assumption that the client asked std::remove to remove c (and presumably all of c's resources) is simply not true. The client really has to destruct the elements of the tail range to ensure that c is really gone, even if the move-assignment operator calls "clear".
This is the benefit of move assignment "clearing" potentially significant resources aggressively. I believe this benefit outweighs the cost.
My current understanding is that there are more subtle reasons why move assignment should "clear" potentially significant resources aggressively. There is still the open point whether self-move-assignment should be forbidden. I wrote in another mail: "On the other hand, as long as the absence of conclusive counter examples can't be "proved", ruling that move-assignment may generally be implemented as swap will be risky or worse." I think the same should apply to self-move-assignment: As long as it can't be proved that forbidding self-move-assignment does no harm, ruling that self-move-assignment is forbidden will be risky or worse. Regards, Thomas

On Oct 17, 2009, at 12:26 PM, Thomas Klimpel wrote:
Howard Hinnant wrote:
So why do it? What's the benefit even if the cost is small?
Consider std::remove: This is one of the few algorithms in <algorithm> which is not a "resource preserving" permutation. Consider remove({a, b, c, d, e}, c). This will result in the sequence {a, b, d, e, e'}. To achieve this result, the following move assignments will take place:
c = move(d); d = move(e);
The first move assignment is not into a resource-less c. If move assignment into c doesn't "clear", when do those resources go away? For most resources (such as memory), it doesn't really matter when. The resources will still be owned if move-assignment doesn't aggressively destroy them (probably by e').
But for some resources (threads, file handles, mutexes), it will matter if they live too long. The client calling std::remove may or may not destruct e' "soon". But the client asked std::remove to remove c (and presumably all of c's resources). I do not think it would be a good idea for std::remove to also say: Oh, and you better destruct e' too just to make sure c is really gone.
I was initially looking for an example of this sort to "prove" that "clear" is required, but came to realize that I probably won't find one. (The examples Dave gave me are sufficient to "prove" that "clear" is required, but they are related to subtleties of the C++ language and not inherent in moving itself.)
But why do I think that this example doesn't "prove" that "clear" is required? Consider "remove({a, b, c, d, c}, c)" instead of "remove ({a, b, c, d, e}, c)". This will result in the sequence {a, b, d, d', c}. So the assumption that the client asked std::remove to remove c (and presumably all of c's resources) is simply not true. The client really has to destruct the elements of the tail range to ensure that c is really gone, even if the move-assignment operator calls "clear".
That's a good counter example. I should've prefaced my example with something like: The client has already unique'd his sequence and knows that the desired removed element is not at the end of the sequence. I also made another mistake in my last post: unique is not a "resource preserving" permutation.
This is the benefit of move assignment "clearing" potentially significant resources aggressively. I believe this benefit outweighs the cost.
My current understanding is that there are more subtle reasons why move assignment should "clear" potentially significant resources aggressively.
There is still the open point whether self-move-assignment should be forbidden. I wrote in another mail: "On the other hand, as long as the absence of conclusive counter examples can't be "proved", ruling that move-assignment may generally be implemented as swap will be risky or worse." I think the same should apply to self-move- assignment: As long as it can't be proved that forbidding self-move- assignment does no harm, ruling that self-move-assignment is forbidden will be risky or worse.
When move assigning into a resource-less lhs, self move assignment appears to me to be harmless. Note that I do not advocate that move-assignment be generally implemented as swap. I generally advise that move assignment be implemented as: 1. Destroy non-memory resources. 2. Move assign base classes. 3. Move assign data members. 4. Fix up invariants. When 1 and 4 are no-ops, it should be safe to let the compiler default this for you. -Howard

Howard Hinnant wrote:
Note that I do not advocate that move-assignment be generally implemented as swap.
I know. That's exactly the reason why I tried to understand why move-assignment should not be implemented as swap. More specifically, you wrote once that you initially thought move-assignment could be implemented as swap, but changed your mind later.
I generally advise that move assignment be implemented as:
1. Destroy non-memory resources. 2. Move assign base classes. 3. Move assign data members. 4. Fix up invariants.
The open question is whether "1." should first check for self-assignment before destroying the non-memory resources. My point was that as long as it can't be proved that omitting the check can't do any harm, omitting the check will be risky or worse. I know that it will be no problem if the object is in an empty state, but what about the case where the object is in a non-empty state? And even if we come to the conclusion that we are allowed to omit the check for self-move-assignment, would we be allowed to assert against self-move-assignment from a non-empty object? The check against self-move-assignment would only have to happen in case the test for the existence of resources to destroy succeeded, so it won't cost much performance anyway (at least in theory). So either there is a very good reason why such a check is not necessary, or... Regards, Thomas

On Oct 17, 2009, at 3:52 PM, Thomas Klimpel wrote:
I generally advise that move assignment be implemented as:
1. Destroy non-memory resources. 2. Move assign base classes. 3. Move assign data members. 4. Fix up invariants.
The open question is whether "1." should first check for self- assignment before destroying the non-memory resources. My point was that as long as it can't be proved that omitting the check can't do any harm, omitting the check will be risky or worse. I know that it will be no problem if the object is in an empty state, but what about the case where the object is in a non-empty state? And even if we come to the conclusion that we are allowed to omit the check for self-move-assignment, would we be allowed to assert against self- move-assignment from a non-empty object?
I don't believe the standard should really tell you, much less try to enforce, how you write your move assignment operator. The standard should give you guidelines on what it is supposed to do, and how it might be used within the std::lib, and that's about it. I think it would be safe for the std::lib to promise you that it won't self-move- assign your objects, with the possible exception of you calling std::swap(x, x). If std::reserve called swap(x, x), I would consider that a bug in std::reserve. Yes, you should be allowed to check for self-move-assignement, or assert on it, or whatever is best for your code. You should probably refrain from self-move-assigning std types such as vector. -Howard

Howard Hinnant wrote:
I don't believe the standard should really tell you, much less try to enforce, how you write your move assignment operator.
I don't know about what the standard should do. I came to this discussion, because I did some sort of mini-review of the proposed "Boost.Move" library. The Adobe Move Library might have many drawbacks, but its documentation has a section "Implementing a Movable Type", which clearly states the requirements for a "Movable Type" (with respect to the Adobe Move Library). I have the opinion that it will reduce the usefulness of the proposed "Boost.Move" library, if the requirements for a "Copyable and Movable" and a "Movable but Non-Copyable" type (with respect to the proposed "Boost.Move" library) can't be clearly stated (or if there is no agreement on these requirements).
I think it would be safe for the std::lib to promise you that it won't self-move-assign your objects, with the possible exception of you calling std::swap(x, x).
What about "std::copy" used together with "std::move_iterator"?
Yes, you should be allowed to check for self-move-assignement, or assert on it, or whatever is best for your code. You should probably refrain from self-move-assigning std types such as vector.
I think the deeper reason for your position is that you want to propose a statement like "If a function argument binds to an rvalue reference parameter, the C++ standard library may assume that this parameter is a unique reference to this argument." for the standard. I really liked that statement, because I thought all that mattered for it was the semantics of "std::move", and that it would only make sense that whoever called "std::move" also had to ensure that the result could be treated like a true rvalue. But I had to learn that any function returning an rvalue-reference would be in the same boat as "std::move" (I don't know whether this is good or bad). I also had hoped that "T&&" could have a semantics quite close to the semantics of "T", but it won't work. The reason is that even true rvalues will not always be destroyed "early" enough, so there is no way to make the semantics of "T&&" similar to the semantics of "T". So std::swap(x,x) might actually be the more crucial question. When anybody is allowed to assert on self-move-assignment, std::swap(x,x) might become invalid, if std::swap isn't fixed. Two weeks ago, I thought that it would be easy to fix std::swap: template <class T> swap(T& a, T& b) { if (&a != &b) { T tmp(std::move(a)); a = std::move(b); b = std::move(tmp); } } However, I realized today that this fix would be a huge regression, because it would slow down std::swap for POD types, i.e. exactly the types that std::swap is typically used for. Changing the semantics of std::swap(x,x) is not really an option in my opinion, and I don't think that inventing more complicated fixes for std::swap would really solve this issue. One option would be to not move-enable std::swap at all, but the better option is probably to allow self-move-assignment at least in the case where the object has just been moved-from. If there would be agreement between the authors of the move proposal (Howard E. Hinnant, Peter Dimov and Dave Abrahams) on any reasonable option how to solve this, I would have no problem with it. But if the only agreement is that no agreement is needed, then I'm a bit confused. My own opinion is that I would love to see "T&&" have a semantics similar to "T", but it seems that this is not possible. A statement like "If a function argument binds to an rvalue reference parameter, the C++ standard library may assume that this parameter is a unique reference to this argument." would also be nice, but here we run into problems with std::swap (but to not move-enable std::swap would solve these). And because the entire topic is much less trivial than it looks at first sight, I have the impression that erring on the safe side is the better idea, hence self-move-assignment should be allowed. Regards, Thomas

2009/8/12 Ion Gaztañaga <igaztanaga@gmail.com>:
Boost.Container
http://svn.boost.org/svn/boost/sandbox/move/libs/container/doc/html/index.ht...
Boost.Container depends on Boost.Move and Boost.Intrusive so you must download also the post-1.40 version of Intrusive placed in the same
package
(Boost.Container uses new features from Intrusive not included in Boost 1.40). Just take fresh Boost 1.39 folder and overwrite it with package contents.
Looking at Boost.Container docs (not code), reminds me of similar experiments I've done in the past. One thing I find interesting is that we could make flat_map's iterators 'stable': - keep the 'outstanding' iterators in a list and update them when the container changes. Of course, then the list might not be 'flat' - but here's the trick, make the iterators contain a 'next' pointer to the next iterator, so the iterator list doesn't live in flat memory at all, but on the stack(s). I guess this doesn't work for the original purpose of flat_map (ie existing in shared memory), but it makes a nice flat map if you just want it to be contiguous, for memory performance reasons. It makes some operations linear over the number of iterators, but that is usually a small number. or - add a level of indirection - the array of value_types isn't sorted, but instead a separate array of indexes (into the value_type array) IS sorted. Iterators point into the 'stable' value_type array, thus being maintained. You also need a 'back-index' from value_type entry to sorted-index, which gets updated whenever the orderedindex array is sorted. So (using fixed-width font): +-----+-----+-----+-----+ | 1 | 2 | 0 | 3 | ordered indexes +-----+-----+-----+-----+ +-----+-----+-----+-----+ | foo | asd | bar | qwe | values (order they were added) | 2 | 0 | 1 | 3 | back index (and ordinal!) +-----+-----+-----+-----+ ^ | iter to bar --+ iterator to bar points to value array containing bar. To do iterator++, follow back_index to find bar in ordered indexes, then move forward to next index. ie iterator++ is: &value_array[ ordered[iter->back_index + 1] ]. That's a bit extra work, and not always worth it, but maybe it is sometimes? Particularly when the value_type is large and costly to move. Note also that the back index (+ 1) is the 'ordinal' of the value - ie 'bar' is the 2nd item by order, since back_index + 1 == 2 in this case. (Not sure how often that is important, but there it is.) Is stable_vector somewhat similar to that? Or what is the data structure of stable_vector? (Sorry I haven't dug into the code yet to find out - I think it would be nice to mention it in the docs, even if the implementation is suppose to be hidden and separate from the requirements.) Tony

Gottlob Frege escribió:
Is stable_vector somewhat similar to that? Or what is the data structure of stable_vector? (Sorry I haven't dug into the code yet to find out - I think it would be nice to mention it in the docs, even if the implementation is suppose to be hidden and separate from the requirements.) Tony
Joaquín wrote the original stable_vector, read hits post: http://bannalia.blogspot.com/2008/09/introducing-stablevector.html the internal structure is a vector of pointers pointing to heap-allocated nodes. The advantage is that iterator are stable (it's a node-based container) and you get random-access using the auxiliar pointer array. Best, Ion

2009/8/24 Ion Gaztañaga <igaztanaga@gmail.com>
Gottlob Frege escribió:
Is stable_vector somewhat similar to that? Or what is the data structure of stable_vector? (Sorry I haven't dug into the code yet to find out - I think it would be nice to mention it in the docs, even if the implementation is suppose to be hidden and separate from the requirements.) Tony
Joaquín wrote the original stable_vector, read hits post:
http://bannalia.blogspot.com/2008/09/introducing-stablevector.html
the internal structure is a vector of pointers pointing to heap-allocated nodes. The advantage is that iterator are stable (it's a node-based container) and you get random-access using the auxiliar pointer array.
Best,
Ion
So very similar. I just 'allocate' from a vector instead of the heap. Which keeps everything together in memory. ie: I see the standard is now going to have a definition of contiguous: &v[n] == &v[0] + n for all 0 <= n < v.size() To me that is "ordered contiguity". I usually find all I really need is "unordered contiguity": for all 0 <= n < v.size() there exists an m with 0 <= m <= v.size() such that &v[n] == &v[0] + m What should that be called? 'locality' ? Tony

2009/8/24 Ion Gaztañaga <igaztanaga@gmail.com>
Gottlob Frege escribió:
Is stable_vector somewhat similar to that? Or what is the data structure of stable_vector? (Sorry I haven't dug into the code yet to find out - I think it would be nice to mention it in the docs, even if the implementation is suppose to be hidden and separate from the requirements.) Tony
Joaquín wrote the original stable_vector, read hits post:
http://bannalia.blogspot.com/2008/09/introducing-stablevector.html
the internal structure is a vector of pointers pointing to heap-allocated nodes. The advantage is that iterator are stable (it's a node-based container) and you get random-access using the auxiliar pointer array.
Best,
Ion
Any thoughts (from anyone?) on my comments about making flat_map's iterators stable? Then it would be a complete replacement to std::map. Tony

Gottlob Frege escribió:
2009/8/24 Ion Gaztañaga <igaztanaga@gmail.com>
Gottlob Frege escribió:
Is stable_vector somewhat similar to that? Or what is the data structure of stable_vector? (Sorry I haven't dug into the code yet to find out - I think it would be nice to mention it in the docs, even if the implementation is suppose to be hidden and separate from the requirements.) Tony
Joaquín wrote the original stable_vector, read hits post:
http://bannalia.blogspot.com/2008/09/introducing-stablevector.html
the internal structure is a vector of pointers pointing to heap-allocated nodes. The advantage is that iterator are stable (it's a node-based container) and you get random-access using the auxiliar pointer array.
Best,
Ion
Any thoughts (from anyone?) on my comments about making flat_map's iterators stable? Then it would be a complete replacement to std::map. Tony
Making flat_xxx a template adaptor (like stack or similar) and using stable_vector as the underlying type would make flat_stable_vector a replacement for std::map. Best, Ion

Making flat_xxx a template adaptor (like stack or similar) and using stable_vector as the underlying type would make flat_stable_vector a replacement for std::map.
Best,
Ion
Making it a template adaptor is interesting, but using stable_vector would make it no longer 'flat'. Of course using statefull allocators ie a statefull 'flat' allocator might make everything work... Tony

Ion Gaztañaga wrote:
Gottlob Frege escribió:
Any thoughts (from anyone?) on my comments about making flat_map's iterators stable? Then it would be a complete replacement to std::map. Tony
Making flat_xxx a template adaptor (like stack or similar) and using stable_vector as the underlying type would make flat_stable_vector a replacement for std::map.
Best,
Ion
I would prefer it as is, but I haven't had a need for iterator stability. I thought the point of a flat_map/flat_set was to be an as-lightweight-as-possible generic associative container, but still with reasonable (O(log n)) lookup time. Perhaps a policy-based approach is possible, or an entirely separate class, e.g., stable_flat_xxx? - Jeff

Jeffrey Hellrung escribió:
I would prefer it as is, but I haven't had a need for iterator stability. I thought the point of a flat_map/flat_set was to be an as-lightweight-as-possible generic associative container, but still with reasonable (O(log n)) lookup time.
Perhaps a policy-based approach is possible, or an entirely separate class, e.g., stable_flat_xxx?
Yes, I was thinking about an entirely separate class.
- Jeff
Best, Ion

On 8/24/09, Jeffrey Hellrung <jhellrung@ucla.edu> wrote:
Ion Gaztañaga wrote:
Gottlob Frege escribió:
Any thoughts (from anyone?) on my comments about making flat_map's iterators stable? Then it would be a complete replacement to std::map. Tony
Making flat_xxx a template adaptor (like stack or similar) and using stable_vector as the underlying type would make flat_stable_vector a replacement for std::map.
Best,
Ion
I would prefer it as is, but I haven't had a need for iterator stability. I thought the point of a flat_map/flat_set was to be an as-lightweight-as-possible generic associative container, but still with reasonable (O(log n)) lookup time.
I think the point was to have a map that could be shared in interprocess memory. At least that was the original idea. But that could now be done with a std::map if we had stateful allocators.
Perhaps a policy-based approach is possible, or an entirely separate class, e.g., stable_flat_xxx?
If you had a stable one, which was this more maplike, should it be called flat_map, and the unstable on be renamed? To sorted_vector or something? By either implementing stableness or renaming, I'm trying to minimize potential misuse. I do understand that stableness isn't the most often used feature, but it might catch some by surprise. Probably not the developer that puts in the flat_map, but by the next one that comes by to use it. Tony
- Jeff
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

On 08/12/2009 11:35 AM, Ion Gaztañaga wrote:
Boost.Container
http://svn.boost.org/svn/boost/sandbox/move/libs/container/doc/html/index.ht...
according to [1] boost.container works with stateful allocators. browsing through the boost.container documentation, i didn't find a comment about it, though ... if this is the case, it would be great to see an example and a section in the documentation on how to use it ... it would be very useful, e.g. for thread-local memory pools ... cheers, tim [1] http://permalink.gmane.org/gmane.comp.lib.boost.devel/192670 -- tim@klingt.org http://tim.klingt.org Art is either a complaint or do something else John Cage quoting Jasper Johns

Tim Blechmann escribió:
On 08/12/2009 11:35 AM, Ion Gaztañaga wrote:
Boost.Container
http://svn.boost.org/svn/boost/sandbox/move/libs/container/doc/html/index.ht...
according to [1] boost.container works with stateful allocators. browsing through the boost.container documentation, i didn't find a comment about it, though ...
if this is the case, it would be great to see an example and a section in the documentation on how to use it ... it would be very useful, e.g. for thread-local memory pools ...
Ok. I supports staful allocators because Interprocess allocators are stateful, but I should add a section with an example. Best, Ion

Hi Ion, It appears that the value_type of a boost::container::vector<...>::const_iterator is const-qualified. Is this the correct semantics? I was under the impression that, generally speaking, value_type's are stripped of const qualification, but I could very well be wrong. I recently replaced some uses of std::vector with boost::container::vector in my own code, and the difference in the const-qualification of value_type triggers some MPL assertion failures... Thanks, - Jeff

Jeffrey Hellrung escribió:
Hi Ion,
It appears that the value_type of a boost::container::vector<...>::const_iterator is const-qualified. Is this the correct semantics? I was under the impression that, generally speaking, value_type's are stripped of const qualification, but I could very well be wrong.
I think you are right (at least other implementations don't const-qualify value_type). I'll see if this error is also in other containers. Best, Ion

Hi (again) Ion, I recently ran into the following issue. I defined a copyable-and-movable class X, and class Y has a member variable of type X and no other member variables. It thus (apparently) happens that Y's auto-generated operator= takes a non-const reference to Y, which I would guess is generally not desired, or at least an operator= overload taking a const reference to Y should be available. For whatever reason, MSVC9 didn't complain at all about using the auto-generated operator=, but when a friend tried compiling code with GCC on Ubuntu, GCC complained about no matching function call to operator=(const Y) (or something like that). The error occurred within a concept checking class, and I would've assumed that MSVC9 was looking at the same code and should've come to the same conclusion; I haven't investigated this further. In any case, we added a trivial Y::operator=(const Y&) explicitly, but GCC didn't like this overload, and it finally quieted down once we added *both* Y::operator=(Y&) and Y::operator=(const Y&). Unfortunately, MSVC9 gives warnings about several operator= overloads, so right now we only have those explicit overloads of operator= when compiling with GCC. The only point I want to make is that it seems worth mentioning this subtlety (at least it was a subtlety to me) in the move documentation somewhere, and perhaps how to most easily and portably address this (I'm not sure what that would be), as I would expect it to be a not uncommon situation. - Jeff

Jeffrey Hellrung escribió:
Hi (again) Ion,
I recently ran into the following issue. I defined a copyable-and-movable class X, and class Y has a member variable of type X and no other member variables. It thus (apparently) happens that Y's auto-generated operator= takes a non-const reference to Y, which I would guess is generally not desired, or at least an operator= overload taking a const reference to Y should be available.
Yes, I recently got the same issue in Interprocess. The standard says that the auto-generated assignment takes non-const argument if any of bases or members' assignment is non-const. Since we generated the non-const version with Boost.Move, assignment operators are not generated with const arguments. In this case the only solution is to avoid compiler-generated assignment operator and write an explicit one. Copy constructor does not suffer this because we rely on RVO to avoid copies. The fact that you need to add *both* const and non-const is strange, in my case, just definining the const version in the higher-level class was enough.
The only point I want to make is that it seems worth mentioning this subtlety (at least it was a subtlety to me) in the move documentation somewhere, and perhaps how to most easily and portably address this (I'm not sure what that would be), as I would expect it to be a not uncommon situation.
Yes, I should warn about this issue.
- Jeff
Ion

on Thu Sep 03 2009, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
Jeffrey Hellrung escribió:
Hi (again) Ion,
I recently ran into the following issue. I defined a copyable-and-movable class X, and class Y has a member variable of type X and no other member variables. It thus (apparently) happens that Y's auto-generated operator= takes a non-const reference to Y, which I would guess is generally not desired, or at least an operator= overload taking a const reference to Y should be available.
Yes, I recently got the same issue in Interprocess. The standard says that the auto-generated assignment takes non-const argument if any of bases or members' assignment is non-const. Since we generated the non-const version with Boost.Move, assignment operators are not generated with const arguments. In this case the only solution is to avoid compiler-generated assignment operator and write an explicit one. Copy constructor does not suffer this because we rely on RVO to avoid copies.
The fact that you need to add *both* const and non-const is strange, in my case, just definining the const version in the higher-level class was enough.
:-( This was a weakness of all earlier approaches that was fixed by Adobe's library; I *really* do not want to see it broken by Boost. It's not always something you can work around. Consider putting a movable-and-copyable type in std::pair; now constant pairs can't be copied, which breaks everything. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams escribió:
The fact that you need to add *both* const and non-const is strange, in my case, just definining the const version in the higher-level class was enough.
:-(
This was a weakness of all earlier approaches that was fixed by Adobe's library; I *really* do not want to see it broken by Boost. It's not always something you can work around. Consider putting a movable-and-copyable type in std::pair; now constant pairs can't be copied, which breaks everything.
Well, maybe benefits don't outweigh problems... I added that overload so that assignment moves rvalues instead of copying them, but maybe it's a limitation and we should just live with it. Or maybe the library needs a radical change, I really don't know what would be the best approach. Thanks, Ion

on Tue Sep 08 2009, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
David Abrahams escribió:
The fact that you need to add *both* const and non-const is strange, in my case, just definining the const version in the higher-level class was enough.
:-(
This was a weakness of all earlier approaches that was fixed by Adobe's library; I *really* do not want to see it broken by Boost. It's not always something you can work around. Consider putting a movable-and-copyable type in std::pair; now constant pairs can't be copied, which breaks everything.
Well, maybe benefits don't outweigh problems... I added that overload so that assignment moves rvalues instead of copying them,
Can you explain what that means in detail, please?
but maybe it's a limitation and we should just live with it. Or maybe the library needs a radical change, I really don't know what would be the best approach.
With more information about the problem, maybe I can offer an answer. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

David Abrahams escribió:
Well, maybe benefits don't outweigh problems... I added that overload so that assignment moves rvalues instead of copying them,
In the first version of the library we had two overloads ("const T &" and "rv<T> &") for copy/move constructor and assignments. But if T& operator=(const T&) is defined then: T produce(); T t; //... //Copy assignment called instead of move-assignment! t = produce(); This is suboptimal for assignments, because a move is replaced with a copy. In theory this also happens for copy-constructors but fortunately RVO is a much better approach and in practice this problem only happens with assignments: //In theory old approach would also call the copy constructor //but nearly all modern compilers use RVO. T t(produce()); That was the state of the library when I first proposed it. Then Klaus Trindle proposed an alternative overload set to catch non-const rvalues using "const rv<T> &" (the additional ovreloads "were rv<T> &" and "T&" --> note that is non-const) and that solves the assignment problem: http://lists.boost.org/Archives/boost/2009/06/153266.php However, this approach requires defining a non-const assignment operator, so we have a new problem: compiler-generated copy assignment takes the argument as non-const T &. So right know I have two options: the old approach that produces suboptimal assignments from non-const rvalues and the newer one that is optimal but hurts the compiler generated copy assignment. We need a third alternative: a new overload set that maintains "const T &" and properly catches non-const rvalues. Best, Ion

on Tue Sep 08 2009, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
David Abrahams escribió:
Well, maybe benefits don't outweigh problems... I added that overload so that assignment moves rvalues instead of copying them,
In the first version of the library we had two overloads ("const T &" and "rv<T> &") for copy/move constructor and assignments. But if T& operator=(const T&) is defined then:
T produce();
T t; //... //Copy assignment called instead of move-assignment! t = produce();
This is suboptimal for assignments, because a move is replaced with a copy. In theory this also happens for copy-constructors but fortunately RVO is a much better approach and in practice this problem only happens with assignments:
...and only with assignments that don't already take their argument by value. std::vector assignment would be an example of something that can't.
So right know I have two options: the old approach that produces suboptimal assignments from non-const rvalues and the newer one that is optimal but hurts the compiler generated copy assignment.
We need a third alternative: a new overload set that maintains "const T &" and properly catches non-const rvalues.
Hmm, tough one. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

On Sep 8, 2009, at 5:52 AM, Ion Gaztañaga wrote:
We need a third alternative: a new overload set that maintains "const T &" and properly catches non-const rvalues.
I think we have it: define one copy assignment operator in terms of swap. Copy elision makes it nearly as fast as a move, and we don't get an operator=(T&) infection in derived classes. I have no problem with requiring move-enablers to supply a swap. -- David Abrahams BoostPro Computing http://boostpro.com

On Nov 16, 2009, at 7:46 AM, David Abrahams wrote:
On Sep 8, 2009, at 5:52 AM, Ion Gaztañaga wrote:
We need a third alternative: a new overload set that maintains "const T &" and properly catches non-const rvalues.
I think we have it:
define one copy assignment operator in terms of swap. Copy elision makes it nearly as fast as a move, and we don't get an operator=(T&) infection in derived classes.
I have no problem with requiring move-enablers to supply a swap.
In fact, I don't even think we need to do that. This seems to work pretty well: #define BOOST_COPYABLE_AND_MOVABLE(TYPE)\ public:\ TYPE& operator=(TYPE t)\ { return this->operator=(static_cast< ::boost::rv<TYPE>&>(t)); } \ public:\ operator ::boost::rv<TYPE>&() \ { return *reinterpret_cast< ::boost::rv<TYPE>* >(this); }\ operator const ::boost::rv<TYPE>&() const \ { return *reinterpret_cast<const ::boost::rv<TYPE>* >(this); }\ private:\ // -- David Abrahams BoostPro Computing http://boostpro.com

David Abrahams escribió:
On Nov 16, 2009, at 7:46 AM, David Abrahams wrote: m with requiring move-enablers to supply a swap.
In fact, I don't even think we need to do that. This seems to work pretty well:
#define BOOST_COPYABLE_AND_MOVABLE(TYPE)\ public:\ TYPE& operator=(TYPE t)\ { return this->operator=(static_cast< ::boost::rv<TYPE>&>(t)); } \ //
Interesting. This works for some types but it has performance impact in types likevector because they can't reuse previous allocated resource (e.g. capacity). Ion

on Mon Nov 16 2009, Ion Gaztañaga <igaztanaga-AT-gmail.com> wrote:
David Abrahams escribió:
On Nov 16, 2009, at 7:46 AM, David Abrahams wrote: m with requiring move-enablers to supply a swap.
In fact, I don't even think we need to do that. This seems to work pretty well:
#define BOOST_COPYABLE_AND_MOVABLE(TYPE)\ public:\ TYPE& operator=(TYPE t)\ { return this->operator=(static_cast< ::boost::rv<TYPE>&>(t)); } \ //
Interesting. This works for some types but it has performance impact in types like vector because they can't reuse previous allocated resource (e.g. capacity).
Yeah, but I think it might be worth the cost. Otherwise you end up with a world where movable types in libraries are poison because, e.g., they are incompatible with std containers. -- Dave Abrahams Meet me at BoostCon: http://www.boostcon.com BoostPro Computing http://www.boostpro.com

David Abrahams wrote:
On Nov 16, 2009, at 7:46 AM, David Abrahams wrote:
On Sep 8, 2009, at 5:52 AM, Ion Gaztañaga wrote:
We need a third alternative: a new overload set that maintains "const T &" and properly catches non-const rvalues. I think we have it:
define one copy assignment operator in terms of swap. Copy elision makes it nearly as fast as a move, and we don't get an operator=(T&) infection in derived classes.
I have no problem with requiring move-enablers to supply a swap.
In fact, I don't even think we need to do that. This seems to work pretty well:
#define BOOST_COPYABLE_AND_MOVABLE(TYPE)\ public:\ TYPE& operator=(TYPE t)\ { return this->operator=(static_cast< ::boost::rv<TYPE>&>(t)); } \ public:\ operator ::boost::rv<TYPE>&() \ { return *reinterpret_cast< ::boost::rv<TYPE>* >(this); }\ operator const ::boost::rv<TYPE>&() const \ { return *reinterpret_cast<const ::boost::rv<TYPE>* >(this); }\ private:\ //
-- David Abrahams BoostPro Computing http://boostpro.com
I think this was similar to what Ion had before, no? Only the user had to explicitly provide the copy assignment (which accepted a by-value parameter). The downside is the double copy of non-movable objects during copy assignment when no copy elision can kick in: copyable_and_movable x(...); copyable_and_movable y(...); x = y; // non-movable stuff in y is copied twice, right? In fact, copyable_and_movable may not even have anything movable in it (if it's templated, e.g., std::pair), so operator=(T) does twice as much work as operator=(const T&) ! The "current" solution (that which is in the sandbox, using BOOST_COPY_ASSIGN_REF) avoids this double copying at the expense auto-generating an operator= that accepts non-const references. Also, as Ion mentioned, currently acquired resources can't be reused if your copy assignment signature accepts by value. That said, I think I'd prefer a by-value operator=, as it seems reasonably close to optimal and gives better semantic behavior regarding auto-generated operator=. Is there some way to generate a (close to) optimal operator= overload for std::pair? I.e., the BOOST_COPYABLE_AND_MOVABLE macro provides one of operator=(const pair&) or operator=(pair) depending on some compiler time boolean value? I'm thinking: template< class T0, class T1 > pair { private: static const bool should_move = T0::should_move || T1::should_move; // obviously not exactly this but the intent should be clear... BOOST_COPYABLE_AND_MOVABLE( pair ) // refers to should_move to decide whether to generate operator=(const pair&) or operator=(pair) public: T0 first; T1 second; }; Something like: template< class T > typename boost::enable_if_c< boost::is_same< T, pair >::value && should_move, pair& >::type operator=(T x); template< class T > typename boost::enable_if_c< boost::is_same< T, pair >::value && !should_move, pair& >::type operator=(const T& x); Is this worth pursuing? Also, the conversion operator to const rv<T>& is a must, as it allows all functions sans T::operator= to be overloaded on const rv<T>&, T&, and rv<T>& to truly capture rvalues by reference. - Jeff

Hey Ion, it's me again, I have roughly the following situation: ---------------- #include <iostream> #include <boost-sandbox/move/boost/move/move.hpp> struct B { B& operator=(const B&) // Auto-generated one is also sufficient... { std::cout << "B::operator=(const B&)" << std::endl; return *this; } // Possibly other overloads of operator=... }; class D : public B { BOOST_COPYABLE_AND_MOVABLE( D ) public: D() { } D(const D&) { } D(BOOST_RV_REF( D )) { } using B::operator=; D& operator=(BOOST_COPY_ASSIGN_REF( D )) { std::cout << "D::operator=(const D&)" << std::endl; return *this; } D& operator=(BOOST_RV_REF( D )) { std::cout << "D::operator=(D&&)" << std::endl; return *this; } }; ---------------- Now, unfortunately, const D source; D dest; dest = source; invokes B::operator=(const B&) rather than D::operator=(BOOST_COPY_ASSIGN( D )), even if it's just the auto-generated one! I've been thinking about the best way to deal with this. In my situations (plural since this has now bit me twice before I figured out the cause), I do want the operator= overloads provided by the base class to be available along with the operator= overloads in the derived class, and this works fine if the derived class has an overload of operator= that accepts a const D& rather than const rv<D>&. Do you (or anyone else) have any suggestions as to the best (cleanest, easiest to maintain, etc.) way to proceed? Also, I think this might be another "gotcha" to add to the documentation. Thanks, - Jeff

Jeffrey Hellrung escribió:
Hey Ion, it's me again,
I have roughly the following situation: [...] Do you (or anyone else) have any suggestions as to the best (cleanest, easiest to maintain, etc.) way to proceed?
Maybe you should just leave assignment operator with const D & and try an explicit move in assigments. At least assignments will be suboptimal (just like before) but it won't break any code.
Also, I think this might be another "gotcha" to add to the documentation.
Yes, but a good issue to discuss in the mailing is if BOOST_COPY_ASSIGN is really a good idea. Best, Ion

Hey Ion, It would be nice if the boost.container classes rebound the allocator type you specify as a template parameter for you, especially since, many times, STL containers end up using a rebound allocator "under the hood" that is implementation-defined, so specifying an allocator with a value_type matching the container's value_type doesn't really buy you anything. I'm guessing this isn't required by the standard, but it looks like your container classes are a strict extension of what the standard requires anyway, and it seems like we wouldn't lose any functionality by auto-rebinding. For vector, for example, I'm thinking something along the lines of template< class T, class Allocator > struct vector { typedef Allocator allocator_type; typedef typename allocator_type::template rebind<T>::other stored_allocator_type; typedef typename stored_allocator_type::value_type value_type; typedef typename stored_allocator_type::reference reference; /* ... etc. ... */ vector(const allocator_type& alloc) : m_stored_alloc(alloc) { /* ... */ } /* ... */ allocator_type get_allocator() const { return m_stored_alloc; } stored_allocator_type& get_stored_allocator() { return m_stored_alloc; } const stored_allocator_type& get_stored_allocator() const { return m_stored_alloc; } private: stored_allocator_type m_stored_alloc; }; That is, the constructor argument and result of get_allocator are precisely what the client specified by the Allocator template parameter, while stored_allocator_type is what's actually used. Is there a good argument against doing this? - Jeff

on Wed Sep 02 2009, Jeffrey Hellrung <jhellrung-AT-ucla.edu> wrote:
Hi Ion,
It appears that the value_type of a boost::container::vector<...>::const_iterator is const-qualified. Is this the correct semantics? I was under the impression that, generally speaking, value_type's are stripped of const qualification, but I could very well be wrong.
You are right. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
participants (19)
-
Christian Schladetsch
-
Christopher Jefferson
-
David Abrahams
-
Frank Mori Hess
-
Gottlob Frege
-
Howard Hinnant
-
Ilya Bobir
-
Ilya Sokolov
-
Ion Gaztañaga
-
Jeffrey Bosboom
-
Jeffrey Hellrung
-
joel
-
Markus Werle
-
Mathias Gaunard
-
OvermindDL1
-
Peter Dimov
-
Steven Watanabe
-
Thomas Klimpel
-
Tim Blechmann