update: we have posted a slideset related to this article
update #2: If N2983 is accepted (as I hope and believe it will be), some of the information in this article will be obsolete. Please read it if you want a complete picture of the issues discussed here.
Welcome to the fifth in our series of articles about efficient value types in C++. In our last episode, we left our intrepid readers vigilantly searching for the optimal implementation of move assignment. Today we’ll find our way through a tough neighborhood of Move City where perfectly ordinary types can clash in suprising ways.
In an earlier posting, we saw that by offering permission to move, one piece of code can work on both movable and non-movable types, taking advantage of move optimizations if and when they are available. This “move if you can; copy if you must” approach is great for optimizing code while keeping it compatible with legacy types that haven’t yet got move constructors. However, it puts a new burden on operations offering the strong guarantee.1
Strong Guarantee, Strong Requirements
Achieving the strong guarantee requires partitioning the steps of an operation into two categories:
- Operations that may throw, but don’t do anything irreversible
- Operations that may be irreversible, but don’t throw.
As long as we can break everything into those two categories, and
everything in category 1 happens before everything in category 2,
we’re home free. A typical example occurs in C++03 when
vector::reserve() has to allocate new element storage:
void reserve(size_type n) { if (n > this->capacity()) { pointer new_begin = this->allocate( n ); size_type s = this->size(), i = 0; try { // copy to new storage: can throw; doesn't modify *this for (;i < s; ++i) new ((void*)(new_begin + i)) value_type( (*this)[i] ); } catch(...) { while (i > 0) // clean up new elements (new_begin + --i)->~value_type(); this->deallocate( new_begin ); // release storage throw; } // -------- irreversible mutation starts here ----------- this->deallocate( this->begin_ ); this->begin_ = new_begin; this->end_ = new_begin + s; this->cap_ = new_begin + n; } }
In a move-enabled implementation, we’d add an explicit call to
std::move in the try block, changing the loop to:
for (;i < s; ++i) new ((void*)(new_begin + i)) value_type( std::move( (*this)[i] ) );
The interesting thing about this change is that, when value_type
is move-enabled, the loop does modify *this (an explicit request
to move from an lvalue is a logically-mutating operation).
Now, if the move operation can throw an exception, the loop makes an
irreversible change, because rolling back a partially-completed loop
would require more move operations. Therefore, to preserve the strong
guarantee when value_type is move-enabled, its move constructor
must not throw.
Upshot
The draft C++0x standard essentially outlaws
throwing move constructors,2 and we
suggest that you follow that rule. However, the rule that
move constructors must not throw isn’t always easy to follow. Take,
for example, std::pair<std::string,UserType>, where UserType
is some class with a throwing copy constructor. In C++03, this type
is well-behaved and can be placed in a std::vector. In C++0x,
however, std::string acquires a move constructor, and so
does std::pair:
template <class FirstType, class SecondType> pair<FirstType,SecondType>::pair(pair&& x) : first(std::move(x.first)) , second(std::move(x.second)) {}
…and here we have a problem. Second is UserType, a type with
no move constructor, which means the construction of second above
is a (throwing) copy, rather than a move. So
pair<std::string,UserType> has acquired a throwing move
constructor, and can no longer be used in a std::vector without
breaking the strong guarantee.
Today, that means we’ll need something like the
following code to make pair work.
1 2 3 4 5 6 7 8 9 10 11 12 | template <class T1, class T2> pair(pair<T1,T2>&& rhs , typename enable_if< // Undocumented optional mpl::and_< // argument, not part of the boost::has_nothrow_move<T1> // public interface of pair. , boost::has_nothrow_move<T2> > >::type* = 0 ) : first(std::move(rhs.first)), second(std::move(rhs.second)) {}; |
The use of enable_if
causes this constructor to “disappear” unless has_nothrow_move yields
true for both T1 and T2.
As far as we know, there’s no way to detect the presence of a move
constructor, much less a nonthrowing one, so until we get some new
language features, boost::has_nothrow_move is going to be one of
those imperfect traits that reports false for user-defined types
unless you specialize it. So, whenever you write a move constructor,
it should be accompanied by a specialization of this trait. For
example, if we added move constructors to std::vector and
std::pair, we’d also add:
namespace boost { // All vectors have a (nothrow) move constructor template <class T, class A> struct has_nothrow_move<std::vector<T,A> > : true_type {}; // A pair has a (nothrow) move constructor iff both its // members do as well. template <class First, class Second> struct has_nothrow_move<std::pair<First,Second> > : mpl::and_< boost::has_nothrow_move<First> , boost::has_nothrow_move<Second> > {}; }
We admit this is all pretty awful. The C++ committee is still discussing the details of how to solve this problem, but a few things are generally agreed:
- We can’t break existing code by silently dropping the strong guarantee.
- The problem can be made much smaller by generating default move
constructors—as suggested by Bjarne Stroustrup in N2904—when appropriate. That will fix the problem for
pairand a whole host of types like it, not to mention speeding up some code “for free” by adding generated move optimization. - There are still types for which we’ll need to deal with this issue “manually.”
The “Problem Types”
Types in the problem category typically have some sub-objects we’d
like to move—provided that can be done safely—and others with which
we need to “do something else.” One example is std::vector, which
contains an allocator whose copy constructor can throw exceptions:
1 2 3 4 5 6 7 8 9 | vector(vector&& rhs) : _alloc( std::move(rhs._alloc) ) , _begin( rhs._begin ) , _end( rhs._end ) , _cap( rhs._cap ) { // "something else" rhs._begin = rhs._end = rhs._cap = 0; } |
A straight member-wise move, like the one generated by default under
N2904, wouldn’t have the right semantics here. In particular, it
wouldn’t zero-out the _begin, _end, and _cap members of rhs.
However, if rhs._alloc doesn’t have a nonthrowing move
constructor, it will be copied in line 2. If that copy can throw, vector
has acquired a throwing move constructor.3
The challenge for the language designers is to avoid asking the user to
supply the same information twice. Having to specify
the types of the members that will be moved in the move constructor’s
signature (lines 5 and 6 in the pair move constructor
above) and then actually move those members in the
constructor’s member initialization list (lines 10 and 11). One
possibility under discussion is to use the new attribute syntax, so
vector’s move constructor might be written this
way:
vector(vector&& rhs) [[moves(_alloc)]] : _begin( rhs._begin ) , _end( rhs._end ) , _cap( rhs._cap ) { rhs._begin = rhs._end = rhs._cap = 0; }
That constructor would be
“SFINAE‘d
out” unless _alloc itself has a nonthrowing move constructor, and
that member would be implicitly initialized by moving from the
corresponding member of rhs.
Unfortunately, there has been some disagreement over the proper role of attributes in C++0x, so we still don’t know what syntax the committee will settle on, but at least we think we understand the problem in principle, and how to address it.
Ramblin’ On
Well, thanks for reading; that’s it for today. Next in this series we’ll discuss perfect forwarding, and we haven’t forgotten that we owe you a survey of C++03 move emulations.
-
The strong guarantee says that if an exception is thrown, there are no effects ↩
-
More precisely, it says that if a move constructor throws during a standard library operation, the behavior is undefined. ↩
-
Fixing the exeception-safety of
vector’s move constructor becomes even more involved than fixingpair’s, which is already templatized. Sinceenable_ifrequires a templated function in order to take effect, we end up with a signature like this one:template <class T2, class A2> vector( vector<T2,A2>&& rhs, boost::enable_if< mpl::and_< boost::is_same<T,T2> , boost::is_same<A,A2> , has_nothrow_move<A> > >* = 0 )



About move_if_noexcept of N2983: It is proposed to be
template <class T> typename conditional< !nothrow_move_constructible<T>::value && has_copy_constructor<T>::value, T const&, T&& >::type move_if_noexcept(T& x) { return std::move(x); }Let me rewrite the return type without double negative so it’s less confusing:
typename conditional< nothrow_move_constructible<T>::value || !has_copy_constructor<T>::value, T&& T const&, >::typeSo, appearently move_if_noexcept also returns an rvalue reference for move-only types regardless of whether the move constructor throws or not. If the function’s name is any indication of a contract this looks like a breach of contract to me.
In addition, there’s a chance that some user wants to use move_if_noexcept for move assignments as well, not just move constructions. But the conditional just checks for a throwing move construction. Now, I realize that for most move-enabled types nothrow_move_constructible<T> will be equal to nothrow_move_assignable<T> but it is also possibe to write a class where only one of these operations throws and the other one doesn’t. Consider a type with a non-throwing move ctor but a throwing move assignment. This definition of move_if_noexcept will allow a throwing move assignment in this case.
What about this one?
template <class T> typename conditional< nothrow_move_constructible<T>::value && nothrow_move_assignable<T>::value, T&& T const&, >::type move_if_noexcept(T& x) { return std::move(x); }This implementation honors its contract. It only returns an rvalue reference if both operations (move construction and move assignment) don’t throw. In rare cases it’ll decrease performance due to being a little more conservative. But I think that’s acceptable. It’ll also lead to some code being rejected. There just isn’t anything you can do about move-only types with a throwing move, is there?
(Quote)Ok, I think I found a wart of my proposal. There’s a category of move-only types that are not assignable. In this case nothrow_move_assignable<T> will be false (because there is no assignment) but that alone must not prevent move_if_noexcept from returning an rvalue reference. It’ll get better if we say what we mean without negations:
template <class T> typename conditional< potentially_throwing_construction<T,T&&>::value || potentially_throwing_assignment<T,T&&>::value T const&, T&&, >::type move_if_noexcept(T& x) { return std::move(x); }I think that’s exactly what move_if_noexcept should do. But I’m not sure if it’s possible to get something equivalent with the traits that are defined in N3000.
(Quote)OK, now I see the advantage of
move_if_noexceptreturningT&&for move-only types. It’s quite possible that overly conservative implementations ofnothrow_xxxwill evaluate to false even for types like unique_ptr with non-throwing move operations. But if we returnT const&in those cases the code won’t compile on some C++ implementations. This is bad. Better: Overly conservativenothrow_xxximplementations should only decrease performance (if anything) but not stop code from compiling. ReturningT&&in those cases is exactly what the proposed move_if_noexcept from N2983 does and I agree that it’s the way to go. In this light, the function’s name is short for “move if no exception can be thrown OR we don’t have a choice w.r.t. move-only types”.But what about move assignment?
After some further pondering I came to the following conclusion (using proposed type traits from N2983):
template<typename T> struct has_potentially_throwing_move_ctor : std::integral_constant<bool,( has_move_constructor<T>::value && !nothrow_move_constructible<T>::value )> {}; template<typename T> struct has_potentially_throwing_move_assign : std::integral_constant<bool,( has_move_assign<T>::value && !nothrow_move_assignable<T>::value )> {}; template <class T> typename conditional< ( has_potentially_throwing_move_ctor<T> && has_copy_constructor<T> ) || ( has_potentially_throwing_move_assign<T> && has_copy_assignment<T> ) , T const&, T&& >::type move_if_noexcept(T& x) { return std::move(x); }The function doesn’t exactly behave like its name suggests — it does return
T&&in some cases even if a move operation potentially throws — so maybe there’s a better name for it. But currently, its behaviour looks like a good compromise to me.I’d love some feedback.
(Quote)“Next in this series we’ll discuss perfect forwarding”
Any news ? I’m really eager to see that one.
It’s odd because in all article on rvalue references I saw (including yours so far
), a big part is always assign to move semantic and very little on perfect forwarding. And yet, I have the feeling that perfect forwarding will be as much important in Ox as move semantic.
(Quote)Hey, I’m in Shanghai at a conference, but I promise I’ll get to this as soon as I get back, next week, if not sooner.
(Quote)Woohoo, thanks !
(Quote)Hi, In the first article of this series, you promised to provide the definition of Value Semantics. I would be very interested if there was any strict definition of the concept. I thought that I ‘feel’ what it is but when I once thought of the definition it all becomes vague. I can think of at least three:
Therefore, a number of questions arise regarding the name ‘Value Semantics’:
I was wondering if I could request an article on the definition of Value Semantics.
Regards, &rzej
(Quote)I’m sure there are several floating around out there. I’m less sure that I would agree with any of them
Those are all in the right ballpark as far as I’m concerned.
You can certainly request, and we do intend to fulfill all of our promises, eventually
Unfortunately, I haven’t thought all the issues through quite enough yet to write that article today. However, I can address your two questions above:
(Quote)Actually, of the above only C++ really supports what I mean by value semantics. Here’s a crucial test: can you write a mutable Matrix type that has the usual copy, assignment, etc. semantics? Of crucial importance is that in this example,
the value of
xis not modified by line 2.For me, syntax matters. Copying an object via
y = x.clone()and especially comparing objects with x.equals(y) don’t countRegarding the analogy between destructors and move constructors, whereas destructors that throw can violate even the basic exception guarantee, the crime of a move constructor that throws is to make an otherwise strongly exception-safe function only provide the weak guarantee. For example, a
vector<T>::reservewhichstd::moves each element on reallocation: it would be strongly exception safe (as required by the Standard) only if C++0x containers were to require no-throw move operations.If C++0x is to allow throwing move operators, I think a
strong_movefunction might be useful. It would be almost identical tostd::move; the sole difference is that it returns an lvalue reference if its type parameter has a move constructor or move assignment operator that throws (i.e. has not been declaredthrow(false)to use Rani’s syntax). Usestd::movein a function only advertising the basic exception guarantee; if the function you’re writing requires the strong guarantee usestrong_move. (std::movewould be sufficient on local variables;strong_moveis intended for objects that outlive the operation, such as vector elements in the case ofvector<T>::reserve).I agree with most of N2904. To incorporate throwing move operations, in the Basic Idea section:
M should be taken to mean ‘has non-throwing move constructor’. This way all generated move operations can be no-throw.
(Quote)Move only types (e.g. unique_ptr) requires some consideration.
I see two options:
1) Disallow throwing move ctor for such (compile-time though I’m fine with UB)
2) Relax relevant operations to provide only basic guarantee for such – no breaking change since the move-only beast is new
From usability POV, “better something than nothing”, I prefer #2 since (unbounded) many clients will anyway prefer speed in context where they don’t care about strong guarantee. Clients that cares about the guarantee will still “enjoy” UB on their own violation (#1 or #2 is same for them). Also for the second, the implementation is easy (AFAICT) – move if no copy (can be encapsulated in utility like right_move()).
OTOH, it might be harder to formulate “conditional strong guarantee” and throwing move only types are probably rare so maybe #1 is fine.
Rani
(Quote)I don’t see why we’d have any problem formulating the conditional strong guarantee.
Also, to be fair, types with throwing move ctors are rare, irrespective of whether they can be copied.
(Quote)In LWG DR #985 I actually asked to relax the strong guarantee in cases that throwing move doesn’t allow it. In the DR, standard containers types are required not to have throwing move ctor to avoid regression (e.g. vector<map>::reserve).
The relaxation only require text about “conditional strong guarantee” without implementation change (e.g. std::move in vector::reserve is fine as is) but some thought that the relaxation is too risky/confusing and therefore I thought about the “conditional non-throwing move optimization” to keep the strong guarantee but still allow throwing move.
Throwing move ctors are rare as throwing default ctors (e.g. Dinkumware’s node based containers) but it seems that move only types are rare by themselves so throwing move ctor for such should be very rare. I personally prefer the “better than nothing” relaxation for the favor of move-only in order to avoid any “rarity analysts”.
Rani
(Quote)Hi Dave,
Your initiative is highly appreciated and I was especially waiting for this installment. We already had some private discussions about move’s exception safety and I still hope to figure better conclusion.
Our basic disagreement is on whether throwing move should be allowed. In LWG DR #985 (http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#985) I made some points on why to allow throwing move and I still think that it’s worth considering.
Stroustrup and you compared move constructor to destructor hence no-throw is required. I’ve pointed that move constructor is like default constructor since it typically leaves the source object in an “empty state” so throwing default ctor yields throwing move constructor. Therefore, IMO, disallowing throwing move is as restrictive as disallowing throwing default ctor (and current MS-VC STL has some).
Another example, as shown from std::pair, is “partial” move in which move constructor of class with members of legacy types will move what it can (optimal move). Note that, as you’ve shown, move assignment is usually like swap though partial move assignment is also possible.
In general, even throwing move is typically faster than full copy so it’s better not to penalties the non-exceptional code path by preferring throwing move over full copy.
So from usability POV it’s better to allow throwing move (relaxation). This is somewhat similar to allowing throwing copy constructor (something that was almost rejected from C++98).
Now let’s consider the (STL) implementation restrictions from allowing throwing move (e.g. inability to optimize). In many use cases, move ctor is used in throwing contexts – when constructing the object before the move. Therefore most operations with strong guarantee are not affected by throwing move (e.g. insert to node based container). The problem is with aggressive move optimizations such as vector’s range move where move from l-value is involved and I think that such cases are not important enough to disallow better usability.
IMO, the user should opt-in for such aggressive optimization using non-throwing move (“opt-in via no-throw”) and the implementation should condition aggressive optimization based on the existence of throwing move (i.e. quality of implementation is allowed).
Also for my suggestion, adding language support for making non-throwing functions detectable is essential. The STL implementation will allow throwing move (e.g. copy if throwing-move) and the optimizer will be allowed to add implicit non-throwing when possible.
It’s also useful, for advanced implementations, to allow (compile time) conditional no-throw operations. For example, std::pair’s move constructor will be non-throwing if both types have non-throwing move. This can be done using syntax like throw(const Boolean expression) – e.g. f::f(T&&) throw(has_throwing_move::value).
IMHO, such “modest” language support will allow safe and optimized code.
Rani
(Quote)I don’t think I ever made that comparison, did I?
Yes.
You are suggesting that operations in this category should only use move when they can prove that move is non-throwing?
Yup. At the very least, the library needs the ability to do that. And if the library can do it, why not the user?
Sorry, I don’t follow this part. What’s “implicit non-throwing?”
Well, that’d sure be nice to have. Unfortunately, adding a feature like statically-checked exception specifications (essentially what you’re proposing, (and what Doug and I proposed with “
noexcept“) at this late stage of standardization would face significant opposition. I think we decided that Bjarne’s suggestion to give move construction first-class status and simply declare it to be nonthrowing was simpler. However, now that I look at it, I am no longer convinced. When I add up all the complexities, I must admit that I find your arguments compelling.To me, it even looks like we could allow
(Quote)std::pairto acquire a throwing move ctor without breaking any user code, because the only code whose semantics depend on whether a move throws must usestd::move. I.e., no such code exists yet.(I wasn’t able to figure out how to indent the post in your nice style)
Rani >> Stroustrup and you compared move constructor to destructor hence no-throw is required.
Dave > I don’t think I ever made that comparison, did I?
I might be wrong but Stroustrup was explicit in N2904 “I suggest that it should be an exact parallel to the rule that destructors should not throw.”
Dave > You are suggesting that operations in this category should only use move when they can prove that move is non-throwing?
Yes. code for which throwing move ctor is not allowed (non preserving optimization) is rare and in some sense is of different category. IMO, the language should allow various QOI in respect to move optimizations such as fallback to “default constructor and non-throwing move assignment” if possible (since move-assignment is similar to swap so it’s less likely to throw hence it make sense to optimize accordingly). detectable no-throw will allow such.
Rani >> making non-throwing functions detectable is essential.
Dave > And if the library can do it, why not the user?
The advanced user can write conditional optimizations but it’s still legitimate to allow throwing move. It seems non realistic to have such restriction on ordinary users when current STL implementations have throwing default constructor hence throwing move ctor (e.g. std::map in MS VC).
Rani >> optimizer will be allowed to add implicit non-throwing when possible.
Dave > What’s “implicit non-throwing?”
In case that the compiler proves that func is not throwing then “is-throwing-operation(func)” will be false even if ‘func’ was not explicitly declared with throw(). Compilers are already doing such no-throw optimizations today (e.g. for generic functions like std::copy when used with non throwing operations). move ctors are usually simple enough for the compiler to deduce that they are not throwing.
Such implicit non-throwing will allow many aggressive optimizations without the burden of verbose no-throw annotations.
Dave > Unfortunately, adding a feature like statically-checked exception specifications at this late stage of standardization would face significant opposition
I know that there is slim chance for that but maybe such feature (both detectable throw and conditional throw) can be optional since anyway it should not change the semantics of the program (at worst there are no aggressive optimizations but no broken code). throw(true/false) should be independent from existing throw-specifications and no run-time impact (i.e. throw from throw(false) is UB).
Rani
(Quote)Hit “reply,” then scroll up and click “(Quote)” next to the date of the post you’re replying to. And read the “Posting” page linked at the top of the page. I’ll try to make this easier someday
Yes, but as you may notice, I had no hand in N2904
I wasn’t arguing otherwise.
Yeah, but if we don’t also mark dtors “
(Quote)noexecpt” (orthrow(false)if you prefer—I like that way of avoiding a new keyword, BTW) by default, it’ll be a huge missed opportunity, and that raises the whole anti-nannying argument. Still, I very much like the direction this is going in, and even though it’s too late for the pre-Santa Cruz mailing, I would be very pleased—in my “copious spare time”—to write a paper on it with you and try to get it considered.Some of my posts pass the ‘Preview’ but submit is not working.
I tried hard to mimic your markdown style but no success yet.
Dave: Yeah, but if we don’t also mark dtors “noexecpt” (or throw(false) if you prefer— I like that way of avoiding a new keyword, BTW) by default, it’ll be a huge missed opportunity, and that raises the whole anti-nannying argument. /Dave
IMO, destructors should be implicitly no-throw since it’s always a bug to throw from such. The few exceptions in which dtor throws (e.g. the “error code or throw” type) can use throw(…) to overwrite the default (though I never personally used type with throwing dtor).
FWIW, in our VC env, the compiler assumes that dtors are not throwing and no-throw is used only for optimizations (i.e. UB on violation). Therefore maybe it’s not “huge missed opportunity” since it’s fine to “mitigate” such standard deficiencies.
Dave: I would be very pleased—in my “copious spare time”—to write a paper on it with you and try to get it considered. /Dave
Same here. notify me how you want to proceed with a proposal.
Thanks, Rani
(Quote)Do you think move operations should default to
(Quote)throw(false)? On the one hand, it would be unfortunate if user-written move operations withoutthrow(false)are ignored by STL (at least by operations with the strong guarantee). On the other, it’s quite easy to accidentally violate thethrow(false)from within a move constructor, for example if the class contains a resource holding type without move operations.No. default should be might throw but the compiler is free to suggest otherwise based on inlining since most move operations should trivial enough for the optimizer that is anyway doing no-throw analysis (e.g. has_non_throwing_move can return true even the user didn’t explicitly said so).
AFAICT, most are using exception specification just to override the default for no-throwing and expecting them to do so in reverse (override no-throw) is not realistic.
Note that not all operations with strong guarantee should be pessimized since the problem is mainly related to “lvalues range move” as suggested for std::vector. Strong operations like std::list::insert are not affected since existing elements are not relocated and the new element will be thrown on exception (I have specified the affected operations in LWG DR #985).
Rani
(Quote)“Therefore, IMO, disallowing throwing move is as restrictive as disallowing throwing default ctor (and current MS-VC STL has some).”
I’m not sure I agree. The alternative to a throwing default constructor is a non-throwing default constructor. The alternative to a throwing move constructor is a throwing copy constructor. Not the same thing at all.
(Quote)So if you need/want to write a class whose invariant includes a resource-less empty state (as required for a non-throwing default constructor), you can still do that. You simply don’t get to have a move ctor in that case. Makes sense.
I’m not sure I like that trade-off, but I think Peter’s argument is sound.
(Quote)Such alternative is penalizing the use case for which move was intended “a = func()” though for such contexts it doesn’t matter if move is throwing and even on might throw it will be faster than full copy in the normal code path (e.g. with std::map of current move supporting VC).
Beside of optimization opportunities, disallowing by STL is in practice disallowing throwing move altogether and I find it too restrictive (per my above points).
Rani
(Quote)You can still have move assignment (you have two non-empty values which you can swap), just not move construction.
(Quote)But move constructor is used to construct the rvalue even in the context of assignment. E.g. in “T x ; x = func();” move constructor is invoked inside of ‘func’.
Therefore non-throwing move assignment is not very useful when the move ctor is throwing but disallowed by STL.
Rani
(Quote)Don’t forget, we still get copy elision (usually).
(Quote)Great article!
I think N2953 is worth pointing out as well. Though, I think that one of the requirements for generating implicit move ctors (“all members have a move ctor”) could be changed to “all members have a no-throw constructor that takes an rvalue by reference” which won’t rule out non-throwing copy ctors. — Apropos, what happened to “noexcept”?
BTW: I think the second for-loop in your example should read “for(;i– > 0;)” instead of “for(;i > 0; –i)”.
(Quote)Good point; I hadn’t seen it. Thanks for the pointer!
Well, some people began to argue that the associated compile-time checks were going to make users feel nannied and harassed, and at the same time argued that
noexcepttreated a symptom of the problem rather than the root cause (see N2904)—although I have a hard time seeing how generating defaults addresses a root cause when the problem still exists for types likestd::vector<T,A>. In any case, Doug and I thought the idea of generating default copy and move was a very good one, because it makes the problem cases much rarer, once you are willing to accept the premise that a move constructor just shouldn’t throw. So in Frankfurt it looked to us like adding a mechanism to SFINAE-away the offending move ctors was a much simpler change than what we had been proposing. However, from my follow-up to Rani’s comment I think you can see I’m no longer convinced.As for the language change you propose for N2953, you should take that up with the authors of the paper
(Quote)I cannot seem to find the “lines 10 and 11″
(Quote)You must be looking at
(Quote)vector’s move constructor. Try following the link on the word “above”