SG, on 23.08.2010 10:02:
Any updates you would like to share, Alf?
[snip]
I think your suggestion else-thread, about not generating the move ops
automatically when there is a defined assignment op or defined destructor
(unless known that it has empty body), would be the best solution.
I also included user-declared a copy constructor in that list.
Currently, I like this approach. Though, I can't rule out that there
is something that I'm missing so I welcome other feedback. These are
rules that are just a tad more restrictive than the ones proposed in
N3053. And with these rules both Scott's example and yours would be
fine and not break in C++0x mode.
It might seem draconian but I don't think it would exclude much code needlessly,
and I think the Really Important aspect here is that moving is an optimization
and that an optimization shouldn't break things but should have the exact same
effect as without the optimization, only faster or less memory.
Right.
My email records indicate that the "something unspecified" referred to
above was not in the draft, but an assertion made by Alf in a private
email to me that the issues of special move functions, and the
exception safety problem described in N2855 were orthogonal.
This is how I see it. Please correct any mistakes:
N2855:
points out that throwing move constructors (without any additional
compiler-magic to cope with this) poses a problem in terms of
exception safety. Example: class template pair. Depending on the
template parameters it might or might not be a good idea to declare
a move constructor -- that is, if you want to avoid throwing move
constructors. Its proposed solution relies on "concepts", or more
specifically: a requires clause to conditionally support move ops
depending on the template parameters.
N2904:
operates also under the viewpoint that throwing move ops are bad.
The idea is to promote move ops to special functions and let the
compiler figure whether it is "safe" to generate them -- always
having aggregate-like types like pair<A,B> with no invariants
in mind which in my humble opinion is a dangerous way of thinking
here.
N3053:
Refinement of N2904. Slight rule changes.
N3050:
refinement of N2855. Now, throwing move ctors are deemed to be okay
if we get a little more compiler magic accessible via the noexcept
operator and the nothrow_xxx traits. With this in our hands we are
able to solve the exception safety issue by either suppressing
move ops via SFINAE and/or by allowing throwing moves by marking
them with noexcept(false) so that std::move_if_noexcept falls back
on a copy if we need the strong exception safety guarantee.
But we're left with possibly dangerous rules of N3053 that lead also
to implicitly generated move ops in cases where we DON'T have simple
aggregate-like types and where old code can break in C++0x mode.
If the rules are changed to be a little more restrictive like I
suggested in the August 18th post we get rid of most breaking examples
of old code (including Scott's and Alf's code), still have implicitly
generated move ops for many aggregate-like types and can solve the
problem pointed out by N2855 with the noexcept exception specification
and the traits we got since N3050. Since I proposed to disable
compiler-generated move ops in case any special functions are user-
declared and pair/tuple actually need to at least have user-defined
assignment operators so that "reference members" are correctly dealt
with, we would have to write a pair/tuple class template with user-
defined move ops like this:
template<class T, class U>
struct pair {
T first;
U second;
...
pair(pair && p) noexcept(
nothrow_constructible<T,T&&>::value
&& nothrow_constructible<U,U&&>::value )
: first (forward<T>(p.first))
, second(forward<U>(p.second))
{}
pair& operator=(pair && p) noexcept(
noexcept(first = forward<T>(p.first ))
&& noexcept(second = forward<U>(p.second)) )
{
first = forward<T>(p.first);
second = forward<U>(p.second);
}
...
};
Of course, if you don't want the move ops at all in cases they are not
exception-safe, we really need something like a requires-clause. I'd
be happy to have one without the whole concepts machinery, actually.
Just a requires-clause that accepts a compile-time bool constant that
depends on some template parameters. It doesn't give us "modular type
checking" in the sense that Doug Gregor explained it in one of his
concepts talks but it would be a newbie-friendly SFINAE replacement
and could be used on non-template members of class templates as well.
But I digress...
I can report that the subject was picked up and briefly discussed on
the internal core language mailing list. As far as I can tell, no
conclusions were reached. I do not know if a core issue was opened on
the subject, nor if someone decided to write a paper on it (with
proposed wording). However a mailing deadline has just passed and the
post-Rapperswil mailing will appear here:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/
some time later this week. If an issue was opened, or a paper
written, you'll see it then and there.
Thanks for the update, Howard! I'm glad to hear that it as been
discussed at all.
Cheers!
SG