Re: auto-generated move assignment and base or member with a
by-value assignment
Am 26.02.2014 14:42, schrieb Krzysztof Czai?ski:
On Monday, February 24, 2014 10:38:12 PM UTC+1, Daniel Kr?gler wrote:
Yes, this needs to be the case, because a operator= overload of the
form
T& T::operator=(T)
was already a copy-assignment operator in previous versions of C++.
But there were no move assignment operators, nor move constructors in
previous C++.
Yes, but it still matters, because move operations where introduced as a
new species of special members in C++11 and all special members belong
exactly to one category, never to two.
[...]
Thank you for explaining this, Daniel, but this did not convince me.
I will try to support my statement, that calling T& T::operator=( T ) is
misleading.
Here seem to be a word missing, was this word "copy assignment operator"?
Consider these classes:
struct A // noncopyable
{
A() = default;
A( A const& ) = delete;
A( A&& ) = delete; // this is redundant, here, isn't it?
It is redundant in the sense that without writing it, there will be no
implicit move-constructor declared for this class, because there exists
a user-declared copy-constructor (12.8 p9 b2).
A& operator=( A ) {}
};
An object of type A can't be copy assigned to or move assigned to,
and yet in the same time it has a public "copy assignment operator".
Come on, in practice A isn't copy assignable, nor move assignable!
You are mixing two different concepts here: One concept is the core
language concept of special member functions and these do specify that
this class has a copy-assignment operator. Having a copy-assignment that
also requires the existence of a move- or copy-constructor is not very
useful, though. But this is not very special for move operations. You
could alternatively declare the destructor as deleted without
introducing any deleted copy/move constructor and the type still would
not be able to copy-assign or even construct in a normal variable
definition.
The other concept is a an expression-based concept from the Standard
Library: Copy/MoveConstructible and Copy/MoveAssignable. Both concepts
are based on valid expressions, but they do not directly depend on the
existence of the core language special member functions. For example a
type without any special move member can still satisfy the
MoveConstructible or MoveAssignable concepts, if it has accessible and
non-deleted special copy member functions.
Trying to deduce from the existence (or absence) of special members (the
core language concept) the expression behaviour (the library concept),
is a false analogy and can lead to surprises. E.g. the following type
does have a deleted default constructor, but still can be
aggregate-initialized:
struct S {
S() = delete;
};
S s1; // Error
S s2{}; // OK, aggregate initialization
struct B // movable only
{
B() = default;
B( B const& ) = delete;
B( B&& ) = default;
B& operator=( B ) {}
};
An object of type B can't be copy assigned to, but can be move assigned
to,
and yet in the same time it has a public "copy assignment operator".
Come on, in practice B isn't copy assignable, but it is move assignable!
Again, you are mixing the two concepts I mentioned above.
struct D // copyable and movable
{
D() = default;
D( D const& ) = default;
D( C&& ) = default;
D& operator=( D ) {}
};
An object of type D can be copy assigned to and truly move assigned to.
I mean truly move assigned, no copy occurs. But it has a public
"copy assignment operator". Come on, in practice D is both copy assignable
and move assignable.
I wonder, what should the traits is_copy_assignable and is_move_assignable
report for the above four classes?
These traits are referring to initialization forms and are not bound to
the existence or absence of special members. That was not always the
case. In fact the corresponding TR1 traits like has_trivial_copy,
has_trivial_assign, or has_nothrow_copy were in fact inspecting the
properties of special member functions. In this regard, the new
is_copy/move_* traits are testing "expression" semantics and are more
similar to the Library requirement sets (but do not impose behavioral
requirements) and are often considered as more useful. Still, we have
traits that solely inspect properties of core-language concepts, such as
is_trivial or is_trivially_copyable. For example, the following type is
trivially copyable, albeit it is not default-constructible,
copy/move-constructible, copy/move assignable nor destructible:
struct TC {
TC() = delete;
TC(const TC&) = delete;
TC(TC&&) = delete;
TC& operator=(TC&&) = delete;
TC& operator=(const TC&) = delete;
~TC() = delete;
};
As shown above, a class that has a "copy assignment operator" in practice
behaves like it had other set of assignment operators (except for class
C).
I call that misleading. I think it should be clearly stated, that
any class that has T& operator=( T ) has:
- a copy assignment operator iff it is copy constructable, and
- a move assignment operator iff it is move constructable.
(iff == if and only if)
I hope this shows my concern better.
I have understood that your are comparing the core language definitions
of the special member functions with a different concept set. This was
the reason why I said in my previous response starting with "But I
understand the point that you are trying to make". Please keep in mind
that both concepts domains are different, albeit the core language
concept has impact on the outcome of the library concept.
HTH & Greetings from Bremen,
Daniel Kr?gler
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]