Topic: Comments on rvalue reference (move constructor proposal)
Author: philippe_mori@hotmail.com ("Philippe Mori")
Date: Wed, 26 Feb 2003 18:28:26 +0000 (UTC) Raw View
I have read the move proposal
http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm and I have
some comments and questions on it.
Overload rules and A &&
What will happen in a case like that:
struct B {
};
struct A {
operator B /* #1 */ () /* #2 */ {
return B();
}
};
A a;
void f(A const &); /* #3 */
void f(B &&); /* #4 */
Does f(B &&) will ever be considered?
And if I add or remove cv qualification somewhere
(#1, #2, #3, #4), does it changes something?
Would it be the same if instead of a conversion
operator in A we would have a conversion constructor
in B?
---
What are the rules when there is an overload by value
and one by rvalue reference:
class C { };
void f2(C c);
void f2(C &&c);
Does we have the same rules as for rvalue vs lvalue
reference in that case?
---
What are the rules for function with multiples
arguments where for some of them an overload
provide a better match for lvalue vs rvalue
while for another argument, there is a better
match according to another rule.
---
I am not sure that we should uses static_cast
to cast between lvalue and rvalue for the
following reasons:
a) the same cast operator would be able to do
two unrelated kind of cast which seems
contracdictory with the current pratice that
each C++ cast operator only allows one kind
of conversion.
An example:
class Base1 { };
class Der1 { };
void g2(Der1 &&);
void g1(Base1 &r) {
// Problem - 2 unrelated conversions
// in a single cast.
g2(static_cast<Der1 &&>(r));
}
I think that it would be far more preferable
to have another cast operator. But maybe only
allowing cast to rvalue if the lvalue is already
of the proper type would be enough...
Thus in the sample above, we would need 2
distinct casts like that:
g2(static_cast<Der1 &&>(static_cast<Der1 &>(r)));
In that case, should we allows direct casting from
a rvalue of one type to a rvalue of another type
provide that the cast is legal for lvalue.
void g4(Der1 &&);
void g3(Base 1 &&rr) {
g4(static_cast<Der1 &&>(rr));
}
b) I think that in most situation, it would be
intersting if we do not have to provide the type
by having something like:
g4(rvalue(rr));
Can we do it from a template or it would require
rvalue operator to be supported by the language?
i.e. Would the following code works:
template <typename T>
T && rvalue(T &r) // or even T &&r ?
{
return r; // or return static_cast<T &&>(r);
}
And would it also work for cv qualified parameters
or overloads would be required?
---
For template argument deduction with A &&, what
will happen in a case like that:
template <typename T>
void h(T &&t1, T &&t2) {
}
struct D { };
D d;
h(d, D());
Will it compile?
If so, what will be the result?
a) h<D>(D &&, D &&)
b) h<D &>(D & &&, D & &&)
c) h<?>(D & &&, D &&) -> h(D &, D &&)
Also I think that a T parameter would never be
deduced to be of MyClass && type. Is it the case?
---
Does the perfect forwarding imply that we would
be able to do the following:
template <typename T> T && verify(T &&r) {
if (!r)
throw something();
return r;
}
and that it will works without ambiguities in all
cases and that we can uses the object with the
appropriate cv qualification (and rvalue/lvalue
reference).
It would be great if it would works for any type
(class, pointer, member pointer, function pointer,
array), any cv qualification and reference
qualification for any type of expression
(object, function call,...).
That is if we can do something with an object,
we can add the validation and still do the same
validation. Some examples:
verify(GetATemporary()).uses_that_temporary;
verify(GetARef()).uses_that_ref;
verify(pointer)->uses_that_pointer;
verify(a_smart_pointer)->uses_it;
verify(create_smart_pointer())->uses_it;
(obj.*verify(p_member_function))(some_args);
*(obj.*verify(p_member_data_ptr)) = new_value;
And I would even be able to replace verify by a
macro (VERIFY) that would pass __LINE__ and
__FILE__ information for example for easier
debugging.
And in release version I should be able to
either to easily avoid any verification
overhead...
---
[ 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.jamesd.demon.co.uk/csc/faq.html ]
Author: hinnant@metrowerks.com (Howard Hinnant)
Date: Wed, 26 Feb 2003 19:53:02 +0000 (UTC) Raw View
In article <tS67a.5490$VG.627520@news20.bellglobal.com>, Philippe Mori
<philippe_mori@hotmail.com> wrote:
| I have read the move proposal
| http://anubis.dkuug.dk/jtc1/sc22/wg21/docs/papers/2002/n1377.htm and I have
| some comments and questions on it.
|
| Overload rules and A &&
|
| What will happen in a case like that:
|
| struct B {
| };
|
| struct A {
| operator B /* #1 */ () /* #2 */ {
| return B();
| }
| };
|
| A a;
|
| void f(A const &); /* #3 */
| void f(B &&); /* #4 */
|
| Does f(B &&) will ever be considered?
The overload resolution rules are intended to build upon the current
rules as closely as possible. Section 13.3.3.1.1, paragraph 3 (of the
C++ standard) has a table which is most helpful. It even lists:
"Lvalue-to-rvalue conversion" among the conversions considered during
overload resolution. Using this table, and considering that B&& is a
reference (but a distinct type from B&) that will also bind to an
rvalue, then I conclude that f(B&&) will be considered only if fed a B,
but not an A. From 13.3.3.2/2:
| a standard conversion sequence ( over.ics.scs ) is a better
| conversion sequence than a user-defined conversion sequence or an
| ellipsis conversion sequence, and
An "Lvalue-to-rvalue conversion" can be part of a standard conversion
sequence. But A's operator B() can only be part of a user-defined
conversion sequence.
| And if I add or remove cv qualification somewhere
| (#1, #2, #3, #4), does it changes something?
It would be easier to analyze if you showed what the calling sequence
in question is. But at first glance, I think the answer is no, nothing
changes (assuming your call is f(a)).
| Would it be the same if instead of a conversion
| operator in A we would have a conversion constructor
| in B?
I think I would need to see the calling code in order to post a quality
answer.
| What are the rules when there is an overload by value
| and one by rvalue reference:
|
| class C { };
| void f2(C c);
| void f2(C &&c);
|
| Does we have the same rules as for rvalue vs lvalue
| reference in that case?
Yes, same rules. (ambiguous I believe)
| What are the rules for function with multiples
| arguments where for some of them an overload
| provide a better match for lvalue vs rvalue
| while for another argument, there is a better
| match according to another rule.
They will follow from the existing overload resolution rules (which
already take into account lvalue/rvalue conversions). The best
conversion sequence will win, or if there is no conversion sequence
that is best, it will be ambiguous.
| I am not sure that we should uses static_cast
| to cast between lvalue and rvalue for the
| following reasons:
An alternative which has been (unofficially) proposed is:
rvalue_cast<T>(t);
<snip>
| b) I think that in most situation, it would be
| intersting if we do not have to provide the type
| by having something like:
|
| g4(rvalue(rr));
|
| Can we do it from a template or it would require
| rvalue operator to be supported by the language?
|
| i.e. Would the following code works:
|
| template <typename T>
| T && rvalue(T &r) // or even T &&r ?
| {
| return r; // or return static_cast<T &&>(r);
| }
This is beginning to look much like the proposed move() template
function. Perhaps rvalue() is a better name. Perhaps a language
operator would be superior to a library function. All good thoughts
and points you make.
| And would it also work for cv qualified parameters
| or overloads would be required?
If implemented as a template function as you show above, cv qualifiers
are deduced as part of the type of T by existing language rules.
| For template argument deduction with A &&, what
| will happen in a case like that:
|
| template <typename T>
| void h(T &&t1, T &&t2) {
| }
|
| struct D { };
| D d;
| h(d, D());
|
| Will it compile?
| If so, what will be the result?
| a) h<D>(D &&, D &&)
| b) h<D &>(D & &&, D & &&)
| c) h<?>(D & &&, D &&) -> h(D &, D &&)
I have worried about this case too. Imho the template argument
deduction part of the proposal is the weakest part. It is not needed
for move (it may actually interfere with move), but it sure would be
nice for forwarding.
| Also I think that a T parameter would never be
| deduced to be of MyClass && type. Is it the case?
I believe you are correct.
| Does the perfect forwarding imply that we would
| be able to do the following:
|
| template <typename T> T && verify(T &&r) {
| if (!r)
| throw something();
| return r;
| }
|
| and that it will works without ambiguities in all
| cases and that we can uses the object with the
| appropriate cv qualification (and rvalue/lvalue
| reference).
I believe that what you show above will work with or without perfect
forwarding (i.e. the modified template argument deduction clause).
| It would be great if it would works for any type
| (class, pointer, member pointer, function pointer,
| array), any cv qualification and reference
| qualification for any type of expression
| (object, function call,...).
|
| That is if we can do something with an object,
| we can add the validation and still do the same
| validation. Some examples:
|
| verify(GetATemporary()).uses_that_temporary;
| verify(GetARef()).uses_that_ref;
| verify(pointer)->uses_that_pointer;
| verify(a_smart_pointer)->uses_it;
| verify(create_smart_pointer())->uses_it;
| (obj.*verify(p_member_function))(some_args);
| *(obj.*verify(p_member_data_ptr)) = new_value;
You can already get close to this functionality with (lvalue)
references today:
struct something {};
template <typename T>
T&
verify(T& r)
{
if (!r)
throw something();
return r;
}
#include <utility>
int main()
{
std::pair<int, int> p;
std::pair<int, int>* ap = &p;
verify(ap)->first = 1;
std::pair<int, int> const cp(2, 3);
std::pair<int, int> const* apc = &cp;
int i = verify(apc)->first;
}
Just you can't send verify an rvalue! :-) The "move" proposal would
simply allow you to write T&& instead of T&, and thus be able to bind
verify's parameter to an rvalue argument in addition to the
capabilities of today's verify.
Thank you for your interest and feedback on this proposal.
--
Howard Hinnant
Metrowerks
---
[ 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.jamesd.demon.co.uk/csc/faq.html ]