Re: Constrained Forwarding(R-Value Reference)
Howard Hinnant wrote:
I have written:
There are differences between "rvalues", "movability" and "moveable
data type".
Do you agree with the explanation?
Except for rvalue, we have not established sufficiently clear and firm
definitions of these terms to the extent I can either agree or disagree
with your statement.
We have differences in _behaviour_ for each of them, so we need to
make sharp and clear names for each behaviour, and we need name for
each behaviour, so detected behaviour is sufficiently clear and firm
definition of each term.
And my question means: do you agree that we can see some different
behaviours for "move", and that "r-value reference" can not cover very
simple cases of algorithms with moveable data type.
I think, you can try to do the implementation yourself, and place here
result with all error points, that you have found. I have made some examples
with "moveable data type" here, but it is producing nonthing positive (just
have been ignored).
Ok, here is my best attempt with your syntax/design:
template <class FwdIt, class T>
FwdIt
remove(FwdIt first, FwdIt last, const T& value)
{
first = std::find(first, last, value);
if (first != last)
{
FwdIt i = first;
while (++i != last)
{
if (!(*i == value))
{
*first = *i;
++first;
}
}
}
return first;
}
But I believe you replied to this before with:
There is no one declaration of moveable classes here, only copyable, i can
not answer to the kind of question related to undeclared classes.
I don't know where to put the movable declarations for the above generic
algorithm either (using your design). I just know that I want the
statement:
*first = *i;
to use move assignment if it is available, else use copy assignment.
It means you want to overload "operator=" as "move assignment" or as
"copy assignment" by template parameter declaration. You write below,
that
However I believe using the syntax I am showing
*first = *i;
is extremely dangerous in generic code, even if the value_type is
declared movable. The same algorithm might desire to both copy and move
from value_types and having the same syntax for both operations seems
error prone.
There are really two dangerous here:
a) if code support moveable, the code must be explicitly declared as
supported moveable
b) indirect data access, due to usage of iterator (pointer, wrapper
etc)
a) Any algorithm, designed to work with copyable, must be in general
case re-designed to work with moveable also. After that, we must tell
to compiler by declarations, that code is suitable to work with
moveable, for example like this:
template <test moveable class T>
class FwdIt;
template <test class FwdIt, test moveable class T>
FwdIt
remove(FwdIt first, FwdIt last, const moveable T& value);
Instead of "test" we can use "explicit" keyword.
b) Note, that many problems here can appear only due to indirect data
access - due to usage of iterator (pointer, wrapper etc) returning
pointer or reference to moveable. There are many cases when moveable
will be returned by value - there we will have no the problems.
Without iterators we could easy write the example:
class Z;
Z i;
moveable Z first;
//here "move assignment" will be used,
//because "first" declared as "moveable"
first = i;
//here "copy assignment" will be used,
//because "i" declared as "copyable".
i = first;
Also for the variables declared in the example we can use local
moveable/copyable type override with the help of:
a) explicit cast to more limited type:
static_cast<moveable T&:()>
tell to compiler to use "move assignment"
if "move assignment" declared, then must be public
if "move assignment" undeclared, then will be auto-generated
else
if "copy assignment" declared, then must be public
if "copy assignment" undeclared, then will be auto-generated
else
error
b) explicit cast to less limited type:
moveable_cast<T&>
tell to compiler to use "copy assignment"
if "copy assignment" declared, then must be public
if "copy assignment" undeclared, then will be auto-generated
else
error
c) binary explicit specialization
of "assignment by default" (operator=):
operator<-<
tell to compiler to use "move assignment"
by default similar to static_cast<moveable T&:()>,
but can be overloaded by user
operator<+<
tell to compiler to use "copy assignment"
by default similar to moveable_cast<T&>,
but can be overloaded by user
d) with "unary operators"
and copying intermediate value of Z::internal_state
"unary operator move"
operator<
operator<-<
tell to compiler to use "move to Z::internal_state"
operator=(operator<-<)
and then we can "assign from z::internal_state"
"unary operator copy"
operator<+<
tell to compiler to use "copy to Z::internal_state"
operator=(operator<+<)
and then we can "assign from z::internal_state"
Examples of the overriding:
a)
//"move assignment" for copyable
static_cast<moveable typeof(i)&:()>(i)=first;
//explicit "move assignment" for moveable
static_cast<moveable typeof(first)&:()>(first)=i;
b)
//"copy assignment" for moveable
moveable_cast<typeof(first)&>(first)=i;
//explicit "copy assignment" for copyable
moveable_cast<typeof(i)&>(i)=first;
c)
//by declaration of target variable
i = first;
first = i;
//binary operator move for copyable
i<-<first;
//binary operator move for moveable
first<-<i;
//binary operator copy for moveable
first<+<i;
//binary operator copy for copyable
i<+<first;
d)
//unary operator move for copyable
i=<first;
//unary operator move for moveable
first=<i;
//unary operator copy for moveable
first=<+<i;
//unary operator copy for copyable
i=<+<first;
So we can also tell to compiler by declaration inside implementation
of the "remove", that code is using "move assignment", at least to
help to read the code
*first <-< *i;
Let's consider that can be done with the indirect access in order to
do "overload "operator=" as "move assignment" or as "copy assignment"
by template parameter declaration".
1. The one way, we can use template parameter "FwdIt" similar to class
"moveable_iterator".
A. I am repeating the previous possible declaration of template
parameter "FwdIt" as class "moveable_iterator":
//moveable_iterator
template<class T=moveable_class>
class moveable_iterator
{
moveable T auto*:() data;
public:
//here "operator++" allow to do "<i++"
moveable moveable_iterator&:(ctor;...)
operator++ ()ctor;
moveable moveable_iterator
operator++ (const int)ctor;
//moveable reference without attributes
moveable T& operator* (){ return *data; }
//moveable reference without without explicit attributes
//by default means "the attributes can not be changed"
//moveable T&:(...;same)
//("move ctor" and "move assignment" is exception)
//operator move
moveable T::internal_state
operator< ()dtor { return <*data; }
public:
...
};
Take a look on "moveable_iterator::operator*()" declaration:
//moveable reference without attributes
moveable T& operator* (){ return *data; }
Its return value declared as "moveable T&" means you will not change
compile-time attributes of value of "moveable T" via the reference
returned by "moveable_iterator::operator*()".
The constness of compile-time attributes protect you from "move from"
or "move to" the return value of "moveable_iterator::operator*()". In
comparison with semantics of "auto_ptr" this means you will be
protected from ownership changement of "T".
Is the declaration the best or the worst for all possible cases in the
world i do not know, beacause syntax of "moveable data type" gives to
user general tools to free express all necessary properties of
moveable data type. This can be compared with declaration:
const T foo();
is "const T" good or bad? I do not know.
To see correct usage of operator* you cam compare the lines from
implementation of "remove":
//here "*i" has been used correctly.
if (!(*i == value))
//here "*i" and "*first" have been used incorrectly.
*first = *i;
B. By the way, you can declare attributes of return value of
"moveable_iterator::operator*()" to be "not worse than was". This
makes usage of "*first" (in the comparing above) quite correct:
//moveable reference without attributes
moveable T&:(...;!dtor) operator* (){ return *data; }
But in the case you can get async changement of attributes of "*data"
and iterator, so we are forced to make manual correctness of
attributes of iterator:
{
//assuming here "first" and "*first" are "dtor"
//assuming declared:
//"any_type T::operator=(any_modified_T)ctor;"
//ok due to !dtor of "moveable T&" declaration
*first = new T;
//here "*first" is "ctor", but "first" is still "dtor"
//error detected incorrectly
*first;
//we can force correctness of "first"
first:(ctor);
//ok
*first;
}
C. But you can not "move from" reference, returned from
"moveable_iterator::operator*()". The expression:
*first = *i;
is not the best expression to make movement from iterator or wrapper,
because using "operator*" to make move is wrong idea in general,
because (speaking auto_ptr's semantics) we need _two different
operations_ to separate "ownership transfer, move internal value" and
"return POD pointer, get internal value".
In the previous example unary operator move (operator<) has been used:
you must not use "operator*" instead "operator<" to make move.
There are differense between "get POD pointer" and "move ownership".
Now you can be protected from double move for each step
{
*first = <i;
//error detected
*i;
++first;
//ok
*i;
}
2. There are other ways, for example, we can use template parameter
"FwdIt" with operator* allowing movement and refusing from
declarations of control of "double-move error". Look at the changes of
"moveable_iterator":
//moveable_iterator
template<class T=moveable_class>
class moveable_iterator
{
public:
...
//moveable reference without attributes
moveable T&:(...;...) operator* (){ return *data; }
//moveable reference without without explicit attributes
//by default means "the attributes can not be changed"
//moveable T&:(...;same)
//( "ctor", "assignment", "binary move" and "binary copy"
// is exception and its attributes are ":(ctor;dtor)" )
//unary operator move
moveable T::internal_state
operator< ()dtor { return <*data; }
};
we are forced to make manual correctness of attributes of iterator:
{
*first <-< *i;
i:(dtor);
//error detected
*i;
++first;
//ok
*i;
}
In the current rvalue reference proposal, the desire to prefer move else
copy is stated with the following syntax:
template <class FwdIt, class T>
FwdIt
remove(FwdIt first, FwdIt last, const T& value)
{
first = std::find(first, last, value);
if (first != last)
{
FwdIt i = first;
while (++i != last)
{
if (!(*i == value))
{
*first = std::move(*i);
++first;
}
}
}
return first;
}
Now the author of the algorithm is clearly aware that *i may be moved
from (this is not an accidental move).
So I ask again: How would you implement std::remove? Please show with
a full code example rather than bits of code interspersed with prose.
My apologies in advance if you've already shown the implementation of
std::remove. I respectfully request you repost it.
There are two ways of implementation of "std::remove", that first came
up to my mind. Note, the concrete way of implementation can depend
from conventions of concrete library for interface of iterators and so
on:
1. ======
//required interface of "T"
//
class T
{
//moveable
public:
T(const moveable T&);
moveable T& operator= (const moveable T&)ctor;
};
//required interface of "FwdIt"
//
template <test moveable class T>
class FwdIt
{
protected:
moveable T auto*:()
data();
const moveable T auto*:()
data() const;
//data access
public:
//operator access
moveable T &:(...;!dtor)
operator* (){ return *data(); }
const moveable T &:(...;!dtor)
operator* ()const { return *data(); }
//operator access
moveable T auto*:(...;!dtor)
operator-> (){ return data(); }
const moveable T auto*:(...;!dtor)
operator-> ()const { return data(); }
//operator unary move
moveable T
operator< ()const dtor { return *data(); }
//walking over container
public:
FwdIt &:(ctor;...)
operator++ ()ctor;
FwdIt
operator++ (const int)ctor;
char operator ==(const FwdIt&)const;
char operator !=(const FwdIt&)const;
//copyable
public:
//copy attributes from src
FwdIt(const FwdIt& src) copy(src);
FwdIt& operator= (const FwdIt& src) copy(src);
};
//implementation of remove
//
template <test class FwdIt, test moveable class T>
FwdIt
remove(FwdIt first, FwdIt last, const moveable T& value)
{
first = std::find(first, last, value);
if (first != last)
{
FwdIt i=first;
while (++i != last)
{
if (!(*i == value))
{
*first = <i;
++first;
}
}
}
return first;
}
2. ======
//required interface of "T"
//
class T
{
//moveable
public:
T(const moveable T&);
moveable T& operator= (const moveable T&)ctor;
};
//required interface of "FwdIt"
//
template <test moveable class T>
class FwdIt
{
protected:
moveable T auto*:()
data();
const moveable T auto*:()
data() const;
//data access
public:
//operator access
moveable T &:(...;...)
operator* (){ return *data(); }
const moveable T &:(...;...)
operator* ()const { return *data(); }
//operator access
moveable T auto*:(...;...)
operator-> (){ return data(); }
const moveable T auto*:(...;...)
operator-> ()const { return data(); }
//walking over container
public:
FwdIt &:(ctor;...)
operator++ ()ctor;
FwdIt
operator++ (const int)ctor;
char operator ==(const FwdIt&)const;
char operator !=(const FwdIt&)const;
//copyable
public:
//copy attributes from src
FwdIt(const FwdIt& src) copy(src);
FwdIt& operator= (const FwdIt& src) copy(src);
};
//implementation of remove
//
template <test class FwdIt, test moveable class T>
FwdIt
remove(FwdIt first, FwdIt last, const moveable T& value)
{
first = std::find(first, last, value);
if (first != last)
{
FwdIt i=first;
while (++i != last)
{
if (!(*i == value))
{
*first <-< *i;
i:(dtor);
++first;
}
}
}
return first;
}
It is evidently, that the example can be implemented with the help of
"moveable concept" proposal. Take a look to my page http://grizlyk1.narod.ru/cpp_new,
for changed article #11, #14_1 .. #14_3 (last has been created at
last) and other changes.
--
Maksim A. Polyanin
http://grizlyk1.narod.ru/cpp_new
---
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@ncar.ucar.edu ]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]