 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
ppliu Guest
|
Posted: Fri Sep 17, 2004 5:29 pm Post subject: Some Other Problems about the "Move Proposal" ! |
|
|
to Dave:
There's some other questions about the move proposal:
First:
--------------
struct X{};
void f(X&& );
void use()
{
X x;
f(x); //should this result in a compile error?
}
Anyway, a lvalue should *not* be *implicitly* moved(movable)!
And,actually,I think,the example you gave,which only has "f(S&&)"
declared,is a kind of
design flaw.
By keep lvaue from being implicitly *bind*(not cast,because in current
language rule,lvalue
can be implicitly casted to rvalue on the fly) to rvalue reference,some
design error can be
forbidden,for example:
void f(A&& a)
{
pilfer a ,but if there's only this "f" ,it will pilfer lvalue
too!!
so this kind of desin should be forbidden!
}
By the "lvalue not implicitly bind to rvalue reference" rule,resource
of some object will
not be pilfered unexpectly.
This rule can be stated like this:
When some object binds to a rvalue reference,It's because of two
(and only two) reason:
1). the object *requests* to be moved!
2). the object is a temporary object(rvalue),so it *can* be
moved absolutely!
In 1),the object is explicitly casted to a rvalue,and in 2),the
object is rvalue
itself.In both case,the object must be a rvalue.
And, I got another consideration:
If only a move constructor like
A(A&&);
was declared, Should the implicitly generated copy constructor
A(A const& ); or A(A&);
be erased?
For example:
class A
{
public:
A(A&& )
{
// move
}
}
Should this class have a default generated copy constructor?
Here's my opinion:
If we don't generate default copy ctor for a class having a move
ctor,there're such
consequences:
1). If this is a design flaw or oversight of the lib author,when the
object of this class
was to be copied ,a compile error will occur,which says that "can't
bind a lvalue to rvalue
reference" which reminds the author of the error!
2).A class which is movable may have resources almost all the time,such
a class needs deep
copy! By this rule,if one declares move constructor only,and try to
copy construct,compile
error will occur,which reminds the one to modify his class!
[Note! In rare case,some class will be movable and default copyable,in
such cases,users
should add a copy constructor manually. for example:
class A
{
vector<X> xv;
public:
A(A&& a): xv(move(a.xv) )
{}
//should we generate a default copy constructor here?
//if not,the author should add it here,otherwise compile error may
occur.
}
]
3). This may always be a design error,except that It's on purpose.
So,If this is on purpose,the lib author means that objects of this
class are not
copyable but movable,if users try to copy ,compiler error occurs.
BTW,this should rarely happen.
And if we generate default copy ctor for those class which hava move
ctor declared only,the
consequences are:
1). "move construction" and "copy construction" are semantically
separated.
A class that is movable can still be default copy constructable.
If it needs deep copy , the author should declare it explicitly ,which
is always the case in
current language.
If the author forgot to declare a copy constructor in these
situations,as it always be , he
will got a rumtime error in most case.
2). some classes that own its resources by a smart container won't need
a copy constructor
explicitly declared(the defaultly generated one will fits well)while it
has move constructor
declared.
Definitely the move constructor should be declared manually no matter
whether it's *trivial*
or not , here "trivial* means that it delegates all the moving task to
the smart container.
3). In rare case,the author wants to forbid copy construct,and enable
move constructor
meanwhile,he can *only* declare the class like this:
class A
{
public:
A(A&& a);
private:
A(const A& ); // forbid copy constructor *explicitly*
};
This show's the purpose explicitly!
So,what should we do?
Similar problem to operator =.
Also,please correct me if I have misunderstood , and, if not, is this a
rule worth adding to
the c++0x?
At last,
A more subtle problem is : should we generate a default move
constructor for some class?
for example :
class A
{
vector <X> x;
my_container<Y> y;
// other user defined class or std:: class only
}
class B
{
X* px;
Y* py;
}
class A and class B are treated separately,we can introduce two
status:
1). classes without "naked" pointers like X*
2). classes with "naked" pointers
for 1),we can generate a default move constructor and delegates all the
moving task to the
class members.
for 2).we can't,unless we emitt such code for each member pointers:
pointer_name = other.pointer_name;
other.pointer_name = 0;
But there's a problem with it: a pointer may not be for resource
management at all,with
this being the case,we could not generate the code above.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Howard Hinnant Guest
|
Posted: Sat Sep 18, 2004 2:27 pm Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
In article <cie37o$2h0 (AT) odak26 (DOT) prod.google.com>,
"ppliu" <pp_liu (AT) msn (DOT) com> wrote:
| Quote: | There's some other questions about the move proposal:
First:
--------------
struct X{};
void f(X&& );
void use()
{
X x;
f(x); //should this result in a compile error?
}
|
No, it should compile.
| Quote: | Anyway, a lvalue should *not* be *implicitly* moved(movable)!
|
It isn't.
| Quote: | By keep lvaue from being implicitly *bind*(not cast,because in current
language rule,lvalue
can be implicitly casted to rvalue on the fly) to rvalue reference,some
design error can be
forbidden,for example:
void f(A&& a)
{
pilfer a ,but if there's only this "f" ,it will pilfer lvalue
too!!
so this kind of desin should be forbidden!
}
|
There are valid use cases for a non-overloaded void f(A&& a) which have
nothing to do with move semantics. Please see:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html
| Quote: | And, I got another consideration:
If only a move constructor like
A(A&&);
was declared, Should the implicitly generated copy constructor
A(A const& ); or A(A&);
be erased?
|
The implicitly generated copy constructor should follow current language
rules. The introduction of A(A&&) overloads, and does not replace or
inhibit generation of the copy constructor. From N1377:
| Quote: | The move constructor is not a special member. It is like any other
constructor and not like the copy constructor in this regard. Introduction of
a move constructor does not supplant the copy constructor, or inhibit the
implicit definition of a copy constructor. The move constructor overloads the
copy constructor, whether the copy constructor is implicit or not. Similarly
for the move assignment. Move constructors and move assignment are also not
implicitly defined by the compiler should the class author not include them.
Thus no current C++ classes are movable. The class author must explicitly
provide move semantics for his class.
|
| Quote: | For example:
class A
{
public:
A(A&& )
{
// move
}
}
Should this class have a default generated copy constructor?
|
Yes.
| Quote: | At last,
A more subtle problem is : should we generate a default move
constructor for some class?
|
No.
-Howard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
David Abrahams Guest
|
Posted: Sat Sep 18, 2004 2:33 pm Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
"ppliu" <pp_liu (AT) msn (DOT) com> writes:
| Quote: | to Dave:
There's some other questions about the move proposal:
|
Okay. Next time please indent with spaces instead of tabs so your
code is readable. Also leave a blank line between code and text.
| Quote: | First:
--------------
struct X{};
void f(X&& );
void use()
{
X x;
f(x); //should this result in a compile error?
}
Anyway, a lvalue should *not* be *implicitly* moved(movable)!
|
There is no implicit moving here.
| Quote: | And,actually,I think,the example you gave,which only has "f(S&&)"
declared,is a kind of
design flaw. By keep lvaue from being implicitly *bind*(not
cast,because in current language rule,lvalue can be implicitly
casted to rvalue on the fly) to rvalue reference,some design error
can be
forbidden,for example:
void f(A&& a)
{
pilfer a ,but if there's only this "f" ,it will pilfer lvalue
too!!
so this kind of desin should be forbidden!
}
By the "lvalue not implicitly bind to rvalue reference" rule,resource
of some object will
not be pilfered unexpectly.
|
Pilfering can only be intentional. Named rvalue references are
trated as lvalues.
| Quote: | And, I got another consideration:
If only a move constructor like
A(A&&);
was declared, Should the implicitly generated copy constructor
A(A const& ); or A(A&);
be erased?
For example:
class A
{
public:
A(A&& )
{
// move
}
}
Should this class have a default generated copy constructor?
|
IMO no, just for consistency's sake.
| Quote: | Here's my opinion:
If we don't generate default copy ctor for a class having a move
ctor,there're such
consequences:
1). If this is a design flaw or oversight of the lib author,when the
object of this class
was to be copied ,a compile error will occur,which says that "can't
bind a lvalue to rvalue
reference" which reminds the author of the error!
|
But you can bind an lvalue to an rvalue reference. That's key for
perfect forwarding, among other things. Sorry, the rest of your post
is too badly formatted for me to read comfortably. If you want to
fix that up and re-post it, I might try to answer... if Howard
doesn't beat me to it ;-)
--
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
James Hopkin Guest
|
Posted: Mon Sep 20, 2004 7:04 pm Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
David Abrahams <dave (AT) boost-consulting (DOT) com> wrote
| Quote: |
But you can bind an lvalue to an rvalue reference. That's key for
perfect forwarding, among other things.
|
I don't understand why it is key to perfect forwarding.
If lvalues binding to rvalues were disallowed, and forward was
written:
template <class T>
inline
T&&
forward(typename identity<T>::type&& t)
{
return static_cast<T&&>(t);
}
does that not achieve the same thing?
(I'm not arguing against the binding rules - just trying to get a
better understanding)
James
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Howard Hinnant Guest
|
Posted: Tue Sep 21, 2004 11:24 am Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
In article <a0368c9c.0409200155.263b2293 (AT) posting (DOT) google.com>,
[email]jhopkin (AT) reflectionsinteractive (DOT) com[/email] (James Hopkin) wrote:
| Quote: | David Abrahams <dave (AT) boost-consulting (DOT) com> wrote in message
news:<uoek4ir9y.fsf (AT) boost-consulting (DOT) com>...
But you can bind an lvalue to an rvalue reference. That's key for
perfect forwarding, among other things.
I don't understand why it is key to perfect forwarding.
If lvalues binding to rvalues were disallowed, and forward was
written:
template <class T
inline
T&&
forward(typename identity
{
return static_cast<T&&>(t);
}
does that not achieve the same thing?
(I'm not arguing against the binding rules - just trying to get a
better understanding)
|
Perhaps the following is a more accurate statement:
Key to perfect forwarding is that an lvalue of type cv A will bind to:
template <class T>
void f(T&& t);
with T deduced as cv A&. Also key to perfect forwarding is that an
rvalue of type cv A will bind to the same function but with T deduced as
cv A.
So it depends a bit on how you interpret the statement: an lvalue must
bind to an rvalue reference for forwarding. Yes, it does, but in the
process it turns the templated parameter into an lvalue reference. So
whether the lvalue argument bound to an rvalue reference or to an lvalue
reference depends upon your perspective.
Note that the above argument is in the context of a templated forwarder.
There also exist forwarders where the free parameters are not template
parameters. Whether or not these are as useful as the former is
debatable, but they do exist. Consider the binder2nd example in N1690:
template <class Operation>
class binder2nd
: public unary_function<...>
{
...
public:
...
result_type operator()(first_argument_type&& x);
};
Here first_argument_type is not a template parameter that has been
deduced. Rather it is a nested type of the template parameter
Operation. If first_argument_type turns out to be a non-reference (i.e.
the original function was pass-by-value), then the parameter to
binder2nd is an rvalue reference, no matter what type of argument is
passed to it. And it would be preferable if lvalues could bind to it,
since lvalues would also have bound to the original (pass-by-value)
function.
-Howard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
James Hopkin Guest
|
Posted: Wed Sep 22, 2004 6:56 pm Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
Howard Hinnant <hinnant (AT) metrowerks (DOT) com> wrote
| Quote: |
Perhaps the following is a more accurate statement:
Key to perfect forwarding is that an lvalue of type cv A will bind to:
template <class T
void f(T&& t);
with T deduced as cv A&. Also key to perfect forwarding is that an
rvalue of type cv A will bind to the same function but with T deduced as
cv A.
So it depends a bit on how you interpret the statement: an lvalue must
bind to an rvalue reference for forwarding. Yes, it does, but in the
process it turns the templated parameter into an lvalue reference. So
whether the lvalue argument bound to an rvalue reference or to an lvalue
reference depends upon your perspective.
|
I see what you mean. My perspective was that l-values were binding to
l-value refs, courtesy of the argument deduction magic.
| Quote: |
template
class binder2nd
: public unary_function<...
{
...
public:
...
result_type operator()(first_argument_type&& x);
};
|
There seems to be a problem with this, e.g.
struct T {};
struct F : std::binary_function
{
void operator()(int, T);
}
void test()
{
const T t;
binder2nd<F>(F(), 7)(t); // <-- error [*]
}
[*] first_argument_type&& (== T&&) will not bind to const T
(conversion loses qualifiers)
Of course, this could be solved by defining binder2nd<>::operator() as
template <typename Arg>
result_type operator()(Arg&& x);
which doesn't require l-values to bind to r-value refs.
James
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
David Abrahams Guest
|
Posted: Thu Sep 23, 2004 8:10 am Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
[email]jhopkin (AT) reflectionsinteractive (DOT) com[/email] (James Hopkin) writes:
| Quote: | Howard Hinnant <hinnant (AT) metrowerks (DOT) com> wrote
|
| Quote: | template <class Operation
class binder2nd
: public unary_function<...
{
...
public:
...
result_type operator()(first_argument_type&& x);
};
There seems to be a problem with this, e.g.
struct T {};
struct F : std::binary_function
{
void operator()(int, T);
}
void test()
{
const T t;
binder2nd
}
[*] first_argument_type&& (== T&&) will not bind to const T
(conversion loses qualifiers)
Of course, this could be solved by defining binder2nd<>::operator() as
template
result_type operator()(Arg&& x);
which doesn't require l-values to bind to r-value refs.
|
You're quite right. For this particular case I might go back to two
overloads ;-)
--
Dave Abrahams
Boost Consulting
http://www.boost-consulting.com
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Howard Hinnant Guest
|
Posted: Thu Sep 23, 2004 8:11 am Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
In article <a0368c9c.0409220653.36e31f64 (AT) posting (DOT) google.com>,
[email]jhopkin (AT) reflectionsinteractive (DOT) com[/email] (James Hopkin) wrote:
| Quote: | I see what you mean. My perspective was that l-values were binding to
l-value refs, courtesy of the argument deduction magic.
|
I can appreciate that perspective, as it is similar to that when binding
a const type to:
template <class T> void foo(T&); // T can be deduced as const A
But const types can't bind to non-const references! :-)
| Quote: | template <class Operation
class binder2nd
: public unary_function<...
{
...
public:
...
result_type operator()(first_argument_type&& x);
};
There seems to be a problem with this, e.g.
struct T {};
struct F : std::binary_function
{
void operator()(int, T);
}
void test()
{
const T t;
binder2nd
}
[*] first_argument_type&& (== T&&) will not bind to const T
(conversion loses qualifiers)
Of course, this could be solved by defining binder2nd<>::operator() as
template <typename Arg
result_type operator()(Arg&& x);
which doesn't require l-values to bind to r-value refs.
|
favor of bind which is both easier to use and supersets the deprecated
functionality.
So perhaps the motivation for binding lvalues to rvalue references falls
squarely on the "minor applications":
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1690.html#Minor%
20Problems%20Addressed
E.g. consider the author of class A who wants to enable A's use with
rvalue streams:
std::istream& operator>>(std::istream&& is, A& a);
std::ostream& operator<<(std::ostream&& os, const A& a);
If lvalues can not bind to rvalue references then the above list must be
doubled:
std::istream& operator>>(std::istream& is, A& a);
std::istream& operator>>(std::istream&& is, A& a);
std::ostream& operator<<(std::ostream& os, const A& a);
std::ostream& operator<<(std::ostream&& os, const A& a);
In each case the second overload can be trivially implemented:
inline
std::istream& operator>>(std::istream&& is, A& a)
{return is >> a;} // forward to lvalue overload
Further exploring these altered binding rules: The main application of
move semantics will be in overloading A's copy constructor and
assignment operator. Today, all class's have a copy constructor and
assignment operator. Even if they are forgotten or declared private,
they exist. Thus if the author of A adds move semantics and forgets
copy:
struct A
{
A(A&&);
A& operator=(A&&);
};
lvalues will still be drawn to the compiler generated copy constructor
and assignment operator. So the rule to prevent lvalues from binding to
rvalue references has bought you nothing for this case.
However the introduction of the explicit class proposal brings an
interesting slant to the discussion:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2004/n1702.pdf
Consider:
explicit struct A
{
A(A&&);
A& operator=(A&&);
};
Now there is no overloading, and the rule we're discussing does have a
direct impact. If lvalues are allowed to bind to rvalue references then
forgotten copy semantics will result in a run time error. If lvalues
are not allowed to bind to rvalue references then the above class will
be movable, but not copyable (enforced at compile time).
In contrast, with the current proposed binding rules, with or without
explicit classes, to make a non-copyable but movable A looks like:
struct A
{
public: A(A&&);
private: A(A const&);
public: A& operator=(A&&);
private: A& operator=(A const&);
};
So in summary:
Allowing lvalues to bind to rvalue references reduces the workload of
A's author in making A work with rvalue streams (and other minor
applications).
Disallowing lvalues to bind to rvalue reference reduces the workload of
A's author in the context of move semantics for explicit classes,
especially non-copyable ones.
I'm not finding either position to be a killer argument. And I'm
probably neglecting a more convincing argument for one of these
positions.
-Howard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
James Hopkin Guest
|
Posted: Fri Sep 24, 2004 3:31 pm Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
Howard Hinnant <hinnant (AT) metrowerks (DOT) com> wrote
| Quote: |
nod> Or going a step further, just deprecating bind1st, bind2nd in
favor of bind which is both easier to use and supersets the deprecated
functionality.
|
No complaints from me there. It's been a trip down memory lane
thinking about non-boost binders again. :-)
| Quote: |
So perhaps the motivation for binding lvalues to rvalue references falls
squarely on the "minor applications":
|
Out of interest, would you support changing the declaration of
std::swap to
template <class T> void swap(T&&, T&&);
?
Defining swap with these semantics for a non-template client class
would presumably require four function definitions if lvalues can't
bind to rvalue references. That's a lot of client boilerplate.
On the other hand ...
| Quote: | Consider:
explicit struct A
{
A(A&&);
A& operator=(A&&);
};
Now there is no overloading, and the rule we're discussing does have a
direct impact. If lvalues are allowed to bind to rvalue references then
forgotten copy semantics will result in a run time error.
|
.... I find these run-time errors worrying.
It's a finely balanced issue, as far as I can see.
Thanks for all the detailed explanations and examples. I wonder if I
could just trouble you with another question.
Given
struct S { S(S&&); };
const S f();
S s = f();
is it intended that the compiler be allowed to use S(S&&), despite the
return type being const?
Cheers,
James
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Howard Hinnant Guest
|
Posted: Sat Sep 25, 2004 10:33 am Post subject: Re: Some Other Problems about the "Move Proposal" ! |
|
|
In article <a0368c9c.0409230921.70bfca54 (AT) posting (DOT) google.com>,
[email]jhopkin (AT) reflectionsinteractive (DOT) com[/email] (James Hopkin) wrote:
| Quote: | Out of interest, would you support changing the declaration of
std::swap to
template <class T> void swap(T&&, T&&);
?
|
I think the highest quality namespace scope swap solution would allow
zero or one rvalue arguments, but not two. Swapping two rvalues sounds
like it is probably a bug since there would be no observable effect. So
at least for std::swap, we would be looking at 3 overloads to support
such an interface, whether or not lvalues can bind to rvalue references:
template <class T> void swap(T&, T&);
template <class T> void swap(T&&, T&);
template <class T> void swap(T&, T&&);
Whether clients want to go to such efforts to be rvalue-friendly is
another question that I suppose each client will have to answer for them
self.
| Quote: | Thanks for all the detailed explanations and examples. I wonder if I
could just trouble you with another question.
Given
struct S { S(S&&); };
const S f();
S s = f();
is it intended that the compiler be allowed to use S(S&&), despite the
return type being const?
|
No. It is intended that a const S return value will prefer the implicit
S(const S&) overload. I.e. it is anticipated that const return values
will defeat move semantics.
-Howard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|