Re: Implicit move constructor rules in c++0x still badly broken?
On 2011-02-24 05:21, Dragan Milenkovic wrote:
On 02/23/2011 10:31 PM, Daniel Kr?gler wrote:
[..]
For example, the MoveConstructible requirements impose that a type T
that satisfies this requirement set shall support an initialization
T u = rv;
and expression
T(rv)
where rv is an rvalue of T. This is *no* requirement for an existing
move-constructor! It just means that above expressions are well-formed
and that they satisfy further requirements. E.g. the fact that the
destination of this construction is equivalent to the source before
the construction had taken place. In fact, you can satisfy this
requirement if you have a copy-constructor but no move constructor.
This is why I made a conclusion that X(const X &) should not be
treated independently of X(X &&), at least when looking at
the interface of a class. Different combinations of both
can decide whether a class satisfies some of those requirements.
But they are completely different entities and that is a good thing! I
can take advantage of that fact, because I can discriminate between
rvalues and lvalues as arguments. It is my own business, whether I
prefer to have a type that does not match Move/CopyConstructible, if I
still would prefer to realize a special expression behaviour. The core
language is and should not depend on some very special Library concepts.
The MoveConstructible and CopyConstructible concepts (sorry for falling
back to the term 'concept'. It is no longer valid as a part of the C++
language, but it still has the non-language meaning) are special cases.
Many (most?) daily-bread types are designed to satisfy these requirement
sets, but there are still valid reasons to design a class that
intentionally deviates from that.
Here is an example of a type C which has a move-constructor but does
*not* satisfy the MoveConstructible requirements:
class C {
int state;
public:
explicit C(int state) : state(state) {}
C(C&&) : state(42) {}
int get_state() const { return state; }
bool operator==(C rhs) const { return state == rhs.state; }
};
C(1) c1;
const int state1 = c1.get_state();
C(2) c2 = std::move(c1);
assert(c2.get_state() == state1); // Error!
This would be an awkward usage, and I don't wish to go there.
I always make copy (and recently move) operations do what they
are supposed to do. std::auto_ptr's weird copy-ctor and
its destiny are a fine warning against any awkward usages.
What I wanted to point out, is that C++ is a non-intrusive language. It
does *not* enforce a particular design choice. If *you* don't want to go
this route, don't follow it. But asking for changing the core language
to enforce a particular programming style for others is against the
spirit of C++ - and against mine ;-)
I regret that it wasn't possible to make these two declarations
into one declaration, even if it had two different implementations.
(Perhaps it could be an extension for C++1x?)
This won't happen: Note that the special member functions are completely
different from requirement sets.
Let me elaborate:
I start by wanting to make a MoveConstructible and MoveAssignable,
but not CopyConstructible and not CopyAssignable. This is my goal
as a designer of a class.
So now I have to write this into a class. This is why I'm trying
to make a relation between these requirements and special member
functions. The other reason would be to make a reverse, that is
to analyze if a class satisfies some of the requirement sets.
I understand that it is not only the interface that needs to
satisfy, but also preconditions and postconditions, but let's
assume that those are correct for each separate special member
function...
If you want to test, whether your type X satisfies some canonical valid
expressions, you can (static) assert taking advantage of some of the new
type traits, e.g. following the definition of X you could write:
class X {
...
};
static_assert(std::is_move_constructible<X>::value, "Bad boy");
static_assert(std::is_copy_constructible<X>::value, "Bad boy");
static_assert(std::is_move_assignable<X>::value, "Bad boy");
static_assert(std::is_copy_assignable<X>::value, "Bad boy");
This would make it more comprehensible and more clean...
It does not. There is much of a difference between concrete functions
and supported expressions. A single expression requirement can sometimes
be realized by different functions.
You are not talking about some Proxy objects and/or some special
conversions in order to satisfy these requirements, are you?
Not in particular. I only wanted to say that several required expression
may be realized by different means. I can satisfy The EqualityComparable
requirements by providing either a free binary operator== or as a member
function that overloads operator==. I can realize some implicit
conversion requirement by *either* providing a conversion function in my
source type *or* by providing a converting (non-explicit) constructor in
my destination type.
Because, the way I see it now, there are 5 ways to write a class
with value semantics (under the assumption of copy-ctor doing
copy and move-ctor doing move, without awkwardness):
You need to define "value semantics", because...
X1::X1(const X & rhs);
X2::X2(const X & rhs);
X2::X2(X && rhs);
X3::X3(const X & rhs);
X3::X3(X && rhs) = delete;
X4::X4(const X & rhs) = delete;
X4::X4(X && rhs);
X5::X5(const X & rhs) = delete;
X5::X5(X && rhs) = delete;
X1 satisfies MoveConstructible and CopyConstructible
X2 satisfies MoveConstructible and CopyConstructible
X3 satisfies neither
X4 satisfies only MoveConstructible
X5 satisfies neither
... which type would satisfy your criteria of a type with "value semantics"?
Don't you think that those "dual-declarations" (where
each declaration can occur many lines apart) could also
be declared as single declarations without loss of
any meaning and without deviance from any current logic?
That depends on your design aims. You could simply user-declare a
copy-constructor and you are done. This is no business of the core
language, it is just your own programming preference and may depend on
the requirements you want to impose on these types.
So, do you still find my reasoning as incorrect?
Once again, I'm not trying to find a 1-1 relation
between special member functions and requirement sets,
but only to find _some_ relation for the purpose
of design, analysis and overall understanding...
I'm still not sure what you want to realize, but it is IMO very
important to ensure that the C++ core language remains a non-intrusive
language.
Sorry, if this reply does not answer your questions. If not, please
rephrase.
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! ]