Re: Constrained Forwarding(R-Value Reference)
Howard Hinnant wrote:
The if clause might have ended with:
throw_my_exception();
Sorry, i so not understand <what> have ended into "throw_my_exception()".
struct A {...};
A save_state;
void throw_my_exception {throw my_exception();}
void f()
{
A a1;
// ...
if (there_is_an_error)
{
save_state = std::move(a1);
throw_my_exception();
}
// ...
A a2 = std::move(a1); // compile-time error?
// ...
}
I was suspecting it. This is a case when you rely on unknown implementation
of "throw_my_exception()", that the function will never return. You probably
think compiler must take the case into account for moveable (in spite it
will not work even with copyable) and you must not explicit tell to compiler
about the fact. The accident has been considered by me:
You must decide: either function will return, or will not and
write appropriate code.
What do you think about the consideration (skipped by you)?
Do you want to say, that your programs are just total array of pointers
and
you always will use runtime templates? I am not sure.
I was trying to express that the use of move semantics for the templated
functions in <algorithm> is an important application.
I do not argue that "move semantics is important", but
I have gone
through the exercise of implementing <algorithm> and using move in the
appropriate places. I am unclear how your design fares for this task.
Will I need to use the "runtime" keyword to keep the compiler from
flagging an error, either at template definition or instantiation time?
runtime templates can _not_ be controlled in compile time _even for
copyable_. I have written:
2.
Note, in your example you are using "It" as a kind of pointer here
"*first++=". Do you want to be shure, that compiler will warn you about
the
possible "double move"?
Pointers by its definition is simplest kind of "runtime template".
Compiler
can not trace any compile time attributes for runtime templates _even_
for
"copyable data type". Consider:
What do you think about the consideration?
In other words, "moveable data type" can not make the case worse than
copyable do. The purpose of compile time attributes is not control data
hidden by pointers, but to make auto memory and dynamic memory equal.
One can see the steps of "moveable concept" appearence:
*******************************************************
1. Local holder.
================
I have some "holders" (practically using in my programs) to make correct
destruction of dynamic memory on errors or returns. Its memory must be used
only locally in the block:
extern uint field_size;
void get(char *const);
uint read(const char *const);
{
int a(1);
obj_holder<int> b(new int);
array_holder<char> c(new char[field_size]);
get(*c);
if(!*c)safe_throw<err_zptr>("no input string");
*b=read(*c);
if(!*b)break;
for(uint i=*b; i; --i)++a;
return a;
}
There is a problem here: unlike "a", i can not return address of "b" or "c"
outside of the local block. At the "}" destructors of the "holders" will
free its dynamic memory.
2. Moveable concept.
====================
To solve the last problem, we can add "ownership" term into "holder" and
define concrete "ownership transfer" behaviour.
The example of the "concrete ownership transfer behaviour" is auto_ptr
behaviour. The purpose of the auto_ptr to pass ownership to the place of
destanation in spite of auto blocks bounds.
The often application of auto_ptr's ownership transfer is one from unnamed
temporary source to named destanation variable, that is why we no need any
runtime tests to share ownership between some local blocks, because unnamed
temporary unaccessible and will be automatically deleted after transfer has
been completed.
So the kind of "pass" is "move" for auto_ptr. For auto_ptr we can not do
"copy", that is why auto_ptr is not copyable, but moveable. In order to
allow to user to express the fact of moveable data type we need "moveable"
keyword, that can be applied similarly to "const" or "volatile" keywords. We
can not use only "moveable value" or "moveable reference" instead of
ordinary moveable data type.
Of course, we can use auto_ptr with named source variable also, like this:
{
int sa(1);
auto_obj<int> sb(new int(2));
auto_array<char> sc(new char[field_size]);
{
int a(sa);
auto_obj<int> b(sb);
auto_array<char> c(sc);
get(*c);
if(!*c)safe_throw<err_zptr>("no input string");
*b=read(*c);
if(!*b)break;
for(uint i=*b; i; --i)++a;
return a;
}}
There is no problem of access of named source variable when internal block
has been executed here due to "}}" - named source variable is never used
after transfer has been completed.
But it is easy to see, that named source variable is accessible inside
internal block and between "}" in "}}". The access to named source variable
after move has been done is _always error_, never UB.
3. Compile time attributes for moveable data type.
==================================================
The cause of last error is the fact, that boundaries of blocks for "auto"
and "dynamic" memory are not coincide. Take in account, that "non-const"
applied to copyable can not help us to solve the problem, can not protect us
from access to named source variable after move has been done.
To work with "moveable" we must take in account presence of "ownership",
that does not exist for "copyable", so algorithms _can not begin to support
moveable data type_ only by adding "r-value reference" declaration -
algorithms must be _re-designed_ as they must be _re-designed_ to support
"const".
Whithout strict compiler support of detection of improper usage of moveable
data type, it will be _hard to re-design_ old existing code to support new
moveable data type, as it will be hard to re-design old existing code to
support new "const" data type if compiler will not detect "assignment to
const" error.
The purpose of "compile time attributes for moveable data type" is
attenuation boundaries of blocks for "auto" and "dynamic" memory. By nature
of dynamic memory the life-time of variables belonging to the memory is not
conjunction with ordinary blocks boundaries, controlled by compiler.
We are using the dynamic memory to make a kind of blocks for variables
across ordinary blocks boundaries, because we no need to do "copy" to pass
the variable across any ordinary block boundary, so we increase perfomans
here. But simultaneously we do not want to lose reliability and "compile
time attributes for moveable data type" makes this.
With "compile time attributes for moveable data type" the last example
logically looks like this:
{
sa:{
int sa(1);
sb:{
auto_obj<int> sb(new int(2));
sc:{
auto_array<char> sc(new char[field_size]);
{
int a(sa);
sa:}
auto_obj<int> b(sb);
sb:}
auto_array<char> c(sc);
sc:}
get(*c);
if(!*c)safe_throw<err_zptr>("no input string");
*b=read(*c);
if(!*b)break;
for(uint i=*b; i; --i)++a;
return a;
}
sc:{
auto_array<char> sc(new char[field_size]);
sc:}
}
Now we have no errors. Technically, life-time of moveable will be in
accordance with ordinary boundaries "{}", but logically moveable will be
accessible from C++ only inside its logical boundaries "variable_name:{
variable_name:}".
The sintax "variable_name:{ variable_name:}" can be suitable also, but this
can not replace compile time attibutes - declaring the logical boundaries
implicitly by class or function declaration.
outcome:
No one "r-value reference" can solve the 3 steps due to quietly ignoring
existence of the problems, "r-value reference" is too simple to help us (it
implements only small part of possible moveable data type properties, but
hard to upgrade).
*******************************************************
The templated code I showed earlier was a subset of a slightly modified
implementation of std::remove, as coded under the current rvalue
reference proposal.
The code I showed used a helper function: increment(i) to hide the ++i
from the compiler. While this isn't how I coded std::remove, such use
is not unheard of
(http://www.boost.org/libs/iterator/doc/iterator_adaptor.html).
Actually, even without the helper increment() function, the compiler
can't really know what ++i means unless i is a scalar, or it has
immediate access to the definition of operator++() for that type (which
could be in another translation unit or dynamic library).
Again: you are mixing "moveable data type" and template instantiating. "The
compiler can't really know what ++i means" because the template can not be
instantiated at the point of declaration, but in the point of instantiating
compiler will detect all possible errors correctly.
With the help of "moveable" keyword and syntax of "attributes" you can do
all what C++ can do, there are no any limitations, just try to express what
you want, what is really stored in your mind - compiler will detects most of
your errors.
I can not guess it and probably i do not know all possible applications of
"moveable" keyword and syntax of "attributes", due to this is concept
(paradigm, idiom etc), not concrete way, similarly i do not know all
possible situations with "const" usage.
Maybe you are speaking about local attribute overriding? Do you want to
write template with concrete attribute requirements, overriding declared one
in class?
Also "attributes of moveable data type" can not make your example worse than
copyable do. The compiler can't really know what "*i++" means even for
copyable due to pointer, not due to moveable. The purpose of compile time
attributes is not control data hidden by pointers, but to make auto memory
and dynamic memory equal.
template <class FwdIt, class T>
FwdIt
remove(FwdIt first, FwdIt last, const T& value)
again: it is declaration of "copyable" data type "FwdIt" and "T". There are
no any attributes needed here.
{
first = std::find(first, last, value);
if (first != last)
{
FwdIt i = first;
while (++i != last)
{
if (!(*i == value))
{
*first = std::move(*i);
Maybe you want to have with "std::move" local type override here: to force
copyable "*first" to use "move assignment" instead of "copy assignment"?
If "move assignment" undeclared for the concrete copyable "*first", then its
"copy assignment" will be used instead of "move assignment", no real move
will be done and no any attributes needed.
Assuming "move assignment" declared or "*first" points to true moveable data
type.
Maybe you want to protect "std::move(*i)" double move?
{
*first = std::move(*i);
//do you want error detection here?
*i;
++first;
}
again: "compile time attributes for moveable data type" can not make the
case worse than copyable do. The compiler can't really know what "*i++"
means even for copyable only due to _pointer_, not due to moveable.
You probably can to declare here "moveable_iterator" for FwdIt to make sharp
differences between copyable and moveable data types. I think we must not
declare "*first" as "copyable" if you allow "*first" to be moveable only.
The declarations can be like this:
//"base" of moveable data type
class moveable_class
{
public:
//all moveable can declare the type "internal_state"
//class internal_state;
//here "internal_state" is the same as "self class"
typedef moveable moveable_class internal_state;
// for auto_ptr-like wrapper "internal_state" is stored POD
// pointer it is unsafe if exception will occure while
// "internal_state" does not bind to wrapper
// typedef T memory* internal_state;
// "operator move" returns "moveable value"
// of "internal state" its usage is overhead
// in comparison with direct assignment
// and can be unsafe
moveable internal_state
operator< ()dtor{}
public:
moveable_class(){}
//cast from internal_state
moveable_class(const moveable internal_state&:(ctor;dtor)){}
//:(ctor;dtor) by default for "move ctor"
moveable_class(const moveable moveable_class& :(ctor;dtor)){}
//:(ctor;...) and :(ctor;dtor) by default for "move assignment"
moveable moveable_class&:(ctor;...)
operator=(const moveable moveable_class& :(ctor;dtor))ctor
{return *this;}
private:
//not copyable
moveable_class(const moveable_class&);
moveable_class& operator=(const moveable_class&);
};
//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;same)
//("move ctor" and "move assignment" is exception)
//operator move
moveable T::internal_state
operator< ()dtor { return <*data; }
public:
...
};
Take a look, 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;
}
++first;
}
}
}
return first;
}
And perhaps this is instantiated with:
FwdIt = std::list<std::string>::iterator
T = std::string
As i know, std::list<T>::iterator and std::string have no moveable support.
There is no one declaration of moveable classes here.
Is this possible with your design? What is the syntax?
If it does not require the "runtime" keyword, why not?
There is no one declaration of moveable classes here, only copyable, i can
not answer to the kind of question related to undeclared classes. Also
"compile time attributes for moveable data type" can not make the case worse
or better than copyable do.
--
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 ]