Topic: std::move and lvalues
Author: restor <akrzemi1@gmail.com>
Date: Wed, 1 Jul 2009 23:05:43 CST Raw View
> Or is there something that
> says that in a constrained template, the C++98 reference-collapsing
> rules don't apply?
Not in all constrained templates; namely, you do not get ref-
collapsing
if the parameter is constrained so that it cannot be a reference.
> I'm not versed enough in this to know for sure, buy my guess is that
> if you want to get *perfect* forwarding, you'll need to use an
> unconstrained template. After all, the whole idea is that you want to
> forward whatever you get without looking at it, much less constraining
> it.
You are right that for perfect forwarding it makes little sense to
constrain
parameters. However, as a side note, I think you can constrain a
template
w/o constraining its parameters. I think the below is a valid
template:
template< typename T >
requires std::True<true>
class C{};
> My concern about the handling of rrefs in C++0x templates has nothing
> to do with reference collapsing. In C++98, if you see a template like
> this,
>
> template<typename T> void f(T& x);
>
> you know that all instantiated functions take their parameter by
> reference. In C++0x, if you see a template like this,
>
> template<typename T> void f(T&& x);
>
> you do NOT know that all instantiations take an rref parameter. In
> essence, C++0x has defined "T&&" in a template to be a new kind of
> template parameter. This is a fundamentally new idea, and not one I
> have any enthusiasm for. This has nothing whatsoever to do with
> reference collapsing. It's a completely independent thing.
I understand, and share, your objection. Putting it in my words,
symbol && has two unrelated meanings
1. A reference binding to a temporary
2. A "perfect forward" "specifier".
The point I am trying to make is that it will not confuse a programmer
that follows some fairly simple rules.
I still think the problem you see does have a lot to do with reference
collapsing, because "the && surprise" (i.e "T&&" instantiating to l-
ref)
will only happen when reference collapsing happens. Therefore, by
preventing reference collapsing you do prevent "the && surprise".
And the easiest way to prevent reference collapse is to constraint
your type so that it is not a reference. This way you disable perfect
forwarding, but for many non-expert programmers it is not needed
anyway. For example, you don't need perfect forwarding to implement
std::vector.
Perhaps "&&" should be presented differently in C++0x introductions.
Rather than saying "We added an r-value reference that can be used for
perfect forwarding," they should say "We added two new meanings (apart
from logical-OR) for symbol &&: one is a reference that binds to
temporaries, the other is a <<perfect forward specifier>>".
Regards,
&rzej
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Thu, 2 Jul 2009 01:08:00 CST Raw View
restor wrote:
>
> I still think the problem you see does have a lot to do with reference
> collapsing, because "the && surprise" (i.e "T&&" instantiating to l-
> ref)
> will only happen when reference collapsing happens.
Okay, finally I think I see what you mean. Given:
template<typename T>
void f(T&&);
int x;
f(x);
during instantiation of f, x is an lvalue, so T is deduced to be T&,
and the initially generated f is
void f(T& &&);
which collapses down to
void f(T&);
So, yes, reference collapsing is involved. I kept not seeing this,
because x itself is not a reference. But we're in Wonderland now, and
the fact that x is not a reference doesn't mean it won't be treated
as one for purposes of type deduction.
Thanks for pursuing this thread long enough for me to finally see the
light, dim and bleak though it may be.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Thu, 2 Jul 2009 10:56:06 CST Raw View
On Jun 30, 5:30 pm, Scott Meyers <use...@aristeia.com> wrote:
> I'm not versed enough in this to know for sure, buy my guess is that
> if you want to get *perfect* forwarding, you'll need to use an
> unconstrained template. After all, the whole idea is that you want to
> forward whatever you get without looking at it, much less constraining
> it.
Could you give me a pointer, why you think so? There are several
usages already in the suggested constrained library that indeed
takes advantage of perfect forwarding, e.g.
1) [utility]/1:
template <RvalueOf T> RvalueOf<T>::type move(T&&);
2) [pairs]/1:
template<class U, class... Args>
requires Constructible<T1, U&&> && Constructible<T2, Args&&...>
pair(U&& x, Args&&... args);
template<class U, class... Args, Allocator Alloc>
requires ConstructibleWithAllocator<T1, Alloc, U&&>
&& ConstructibleWithAllocator<T2, Alloc, Args&&...>
pair(allocator_arg_t, const Alloc& a, U&& x, Args&&... args);
3) [pairs]/21:
template <MoveConstructible T1, MoveConstructible T2>
pair<V1, V2> make_pair(T1&& x, T2&& y);
4) [tuple.general]/2:
template <MoveConstructible... Types>
tuple<VTypes...> make_tuple(Types&&...);
5) [refwrap]:
template <class... ArgTypes>
requires Callable<T, ArgTypes&&...>
Callable<T, ArgTypes&&...>::result_type
operator() (ArgTypes&&...) const;
6) [func.wrap.func]:
template<class F>
requires CopyConstructible<F> && Callable<F, ArgTypes...>
&& Convertible<Callable<F, ArgTypes...>::result_type, R>
function(F&&);
7) [vector]/2:
template <class... Args>
requires AllocatableElement<Alloc, T, Args&&...>
void emplace_back(Args&&... args);
8) [map]/2:
template <class P>
requires AllocatableElement<Alloc, value_type, P&&> &&
MoveConstructible<value_type>
pair<iterator, bool> insert(P&& x);
9) [algorithms.syn]:
template<RandomAccessIterator Iter, Callable<auto,
Iter::difference_type> Rand>
requires ShuffleIterator<Iter> && Convertible<Rand::result_type,
Iter::difference_type>
void random_shuffle(Iter first, Iter last, Rand&& rand);
to name just a few.
Greetings from Bremen,
Daniel Kr gler
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Thu, 2 Jul 2009 17:11:34 CST Raw View
Scott Meyers wrote:
> restor wrote:
>>
>> I still think the problem you see does have a lot to do with reference
>> collapsing, because "the && surprise" (i.e "T&&" instantiating to l-
>> ref)
>> will only happen when reference collapsing happens.
>
> Okay, finally I think I see what you mean. Given:
>
> template<typename T>
> void f(T&&);
>
> int x;
> f(x);
>
> during instantiation of f, x is an lvalue, so T is deduced to be T&,
> and the initially generated f is
>
> void f(T& &&);
>
> which collapses down to
>
> void f(T&);
>
> So, yes, reference collapsing is involved. I kept not seeing this,
> because x itself is not a reference. But we're in Wonderland now, and
> the fact that x is not a reference doesn't mean it won't be treated
> as one for purposes of type deduction.
>
> Thanks for pursuing this thread long enough for me to finally see the
> light, dim and bleak though it may be.
But it still makes a parameter declared as T && bind to a lvalue.
I fully understand _what_ is going on and why is it _useful_.
The problem is that this syntax and semantics were developed
_especially_ for one purpose.
Couldn't something else worked? Something that would not make T &&
a magic notation?
template <typename & T> void f(T t);
template <typename && T> void f(T t);
template <typename & && T> void f(T t);
int x;
f(10) <=> f<int &&>(10);
f(x) <=> f<int &>(x);
Hopefully, there will be a third reference type, which will make
all this become:
template <typename T> void f(T &&& t);
... leaving && less obscure.
restor wrote:
> The point I am trying to make is that it will not confuse a programmer
> that follows some fairly simple rules.
Yes, this is true. But it doesn't mean there cannot be a better option.
--
Dragan
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Fri, 3 Jul 2009 00:27:29 CST Raw View
Daniel Kr gler wrote:
> On Jun 30, 5:30 pm, Scott Meyers <use...@aristeia.com> wrote:
>> I'm not versed enough in this to know for sure, buy my guess is that
>> if you want to get *perfect* forwarding, you'll need to use an
>> unconstrained template. After all, the whole idea is that you want to
>> forward whatever you get without looking at it, much less constraining
>> it.
>
> Could you give me a pointer, why you think so? There are several
> usages already in the suggested constrained library that indeed
> takes advantage of perfect forwarding
I guess it depends on what is meant by "perfect forwarding." The informal
definition I was using is in my excerpt above: forward whatever you get without
looking at it.
> 1) [utility]/1:
> template <RvalueOf T> RvalueOf<T>::type move(T&&);
This perfectly forwards whatever it gets, but it won't get anything that doesn't
satisfy the RvalueOf concept. But how can such satisfaction be verified without
looking at the argument? In the sense in which I was thinking about the term,
this template doesn't perfectly forward, because it rejects some types -- they
don't get forwarded (at least not via this template; there may be overloads that
do perfectly forward arguments that this template screens out). In another
sense (which is the sense in which I suspect you understand the term), this
template does perfectly forward, because any instantiation does perfect
forwarding.
I don't know if there is an official definition of perfect forwarding. If there
is, I don't know if my informal definition matches it. I suspect that you are
much more familiar with C++0x than I am, and I see nothing inherently
unreasonable about what appears to be your definition, so if you tell me that
your definition is the commonly accepted one and that mine is not, I'm happy to
believe you.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SG <s.gesemann@gmail.com>
Date: Fri, 3 Jul 2009 09:16:53 CST Raw View
Daniel, I want to point out a problem with two of your examples:
On 2 Jul., 18:56, Daniel Kr gler <daniel.krueg...@googlemail.com>
wrote:
>
> 3) [pairs]/21:
> template <MoveConstructible T1, MoveConstructible T2>
> pair<V1, V2> make_pair(T1&& x, T2&& y);
I called this the "rvalue references/cocepts"-gotcha. T1 and/or T2 may
be deduced to be lvalue references but we're not interested in
constraining lvalue references in this case (I suppose) but only the
types the references refer to:
template <RemoveReference T1, RemoveReference T2>
requires MoveConstructible<T1::type>
&& MoveConstructible<T2::type>
pair<V1, V2> make_pair(T1&& x, T2&& y);
(where RemoveReference exposes an associated type so that
RemoveReference<T>::type == typename remove_reference<T>::type).
Otherwise make_pair would not work on lvalues because lvalue
references don't satisfy MoveConstructible (or CopyConstructible for
that matter).
> 4) [tuple.general]/2:
> template <MoveConstructible... Types>
> tuple<VTypes...> make_tuple(Types&&...);
Same problem here.
Cheers!
SG
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Fri, 3 Jul 2009 23:50:34 CST Raw View
On 3 Jul., 17:16, SG <s.gesem...@gmail.com> wrote:
> Daniel, I want to point out a problem with two of your examples:
Thanks for doing so, but I intentionally did not ride
about any current possible issues in the mentioned
declarations. Whether or whether not the signatures
of the quoted examples will be slightly corrected
would not change the overall message I was trying to
give in this reply.
> On 2 Jul., 18:56, Daniel Kr gler <daniel.krueg...@googlemail.com>
> wrote:
>
> > 3) [pairs]/21:
> > template <MoveConstructible T1, MoveConstructible T2>
> > pair<V1, V2> make_pair(T1&& x, T2&& y);
>
> I called this the "rvalue references/cocepts"-gotcha. T1 and/or T2 may
> be deduced to be lvalue references but we're not interested in
> constraining lvalue references in this case (I suppose) but only the
> types the references refer to:
<nod>, but your suggested solution is not the only possible
one. Personally I would suggest to fix MoveConstructible
to make it work for references too, because that would
be more consistent with the C++03 understanding of
CopyConstructible. A class containing references as members
is CopyConstructible (table-requirement CopyConstructible),
and this is easier to define, if a reference is considered
CopyConstructible as well.
> template <RemoveReference T1, RemoveReference T2>
> requires MoveConstructible<T1::type>
> && MoveConstructible<T2::type>
> pair<V1, V2> make_pair(T1&& x, T2&& y);
>
> (where RemoveReference exposes an associated type so that
> RemoveReference<T>::type == typename remove_reference<T>::type).
> Otherwise make_pair would not work on lvalues because lvalue
> references don't satisfy MoveConstructible (or CopyConstructible for
> that matter).
In this case MoveConstructible is the wrong constraint
anyway. What we probably need to require here is something
like Constructible<V1, T1&&> && Constructible<V2, T2&&>.
I think it is possible to redefine concept MoveConstructible to
work for (lvalue-)references as well. One approach is similar to
the usage of Constructible above:
auto concept MoveConstructible<typename T> : Constructible<T, T&&> {
requires IdentityOf<T> && RvalueOf<T>;
}
Note that IdentityOf and RvalueOf are added, but not otherwise
used to ensure that any move process can freely use std::move
or std::forward as appropriate [The latter two need to be added
to the above suggested Constructible replacements of
MoveConstructible in make_pair as well and there are also
missing in some currently constrained templates].
Note that concept MoveAssignable is not affected by this
change. In contrast to initializing expressions, non-initializing
expressions as described in [concept.map.fct]/4 have the
effect that references are handled equally to non-references
(That is basically equivalent to the "remove-references-first"
instructions from [expr]/5+6). This means that (lvalue-)references
satisfy concept MoveAssignable, if their referent types satisfy
concept MoveAssignable.
Greetings from Bremen,
Daniel
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Fri, 3 Jul 2009 23:50:39 CST Raw View
On 3 Jul., 08:27, Scott Meyers <use...@aristeia.com> wrote:
> Daniel Kr gler wrote:
> > On Jun 30, 5:30 pm, Scott Meyers <use...@aristeia.com> wrote:
> >> I'm not versed enough in this to know for sure, buy my guess is that
> >> if you want to get *perfect* forwarding, you'll need to use an
> >> unconstrained template. After all, the whole idea is that you want to
> >> forward whatever you get without looking at it, much less constraining
> >> it.
>
> > Could you give me a pointer, why you think so? There are several
> > usages already in the suggested constrained library that indeed
> > takes advantage of perfect forwarding
>
> I guess it depends on what is meant by "perfect forwarding." The informal
> definition I was using is in my excerpt above: forward whatever you get without
> looking at it.
I agree with your definition argument. The C++ standard does not
define this term and anyone of us can refer to a different one.
Personally I like the definition used in the introduction of
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
"For a given expression E(a1, a2, ..., an) that depends on the
(generic)
parameters a1, a2, ..., an, it is not possible to write a function
(object) f
such that f(a1, a2, ..., an) is equivalent to E(a1, a2, ..., an)."
This definitions doesn't say anything about constraints.
It just mentions the ability for a function call to be equivalent to a
given
expression in all cases [where the expression is valid]. But if E(a1,
a2, ..., an)
itself is a constrained expression (e.g.it could be an expression
involving
constrained templates), then it seems quite natural to me to define
the
perfect forwarding function as a constrained function - at least this
is
required as of the currently suggested language rules if the complete
wrapper is supposed to be also a constrained template.
If we agree that we it makes sense to define something like a
constrained
perfect forwarding function, then we also recognize that the perfect
forwarder
must either *exactly* match the requirements of the forwarded
expression
to really catch all valid cases of E or we intentionally define a more
restricted
"perfect" forwarder that is still perfect in the sense that all
arguments
that *can* enter the function body, will also be perfectly forwarded
as
in the original (unconstrained) form. This can be considered as some
kind of filtering, if you will.
> > 1) [utility]/1:
> > template <RvalueOf T> RvalueOf<T>::type move(T&&);
>
> This perfectly forwards whatever it gets, but it won't get anything that doesn't
> satisfy the RvalueOf concept. But how can such satisfaction be verified without
> looking at the argument? In the sense in which I was thinking about the term,
> this template doesn't perfectly forward, because it rejects some types -- they
> don't get forwarded (at least not via this template; there may be overloads that
> do perfectly forward arguments that this template screens out). In another
> sense (which is the sense in which I suspect you understand the term), this
> template does perfectly forward, because any instantiation does perfect
> forwarding.
IMO perfect forwarding itself is "orthogonal" to constraints, like as
you
can try to solve an equation with possibly more than one solution to
find all possible solutions in unconstrained mode or in constrained
mode, e.g. when you additionally add requirements to the
parameters, like a1 >= a2 (if a1 and a2 are two of the parameters of
the equation). The constrained solution is (in general) different
than
the unconstrained one, but that fact doesn't reduce the worth of
a constrained approach.
Looking back to your mentioned example of std::move: I see nothing
in the deduction mechanism of a constrained function that would
change the mechanism of the original, unconstrained form of
perfect forwarding. The original problem was how rvalues are
forwarded as rvalues and lvalues are forwarded as lvalues. This
principle isn't changed by constraining the forwarding function.
This makes me belief that constraining such a forwarder is similarly
advantageous in many cases.
> I don't know if there is an official definition of perfect forwarding. If there
> is, I don't know if my informal definition matches it. I suspect that you are
> much more familiar with C++0x than I am, and I see nothing inherently
> unreasonable about what appears to be your definition, so if you tell me that
> your definition is the commonly accepted one and that mine is not, I'm happy to
> believe you.
I cannot prove that, I just gave some arguments above which make
at least *me* think to consider the extension of a perfect forwarding
function in constrained context as a useful thing. In fact: Otherwise
we couldn't discuss about perfect forwarding functions in constrained
code - it is simply not defined (if we ignore late_check at this
point).
Greetings from Bremen,
Daniel
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Joe Smith" <unknown_kev_cat@hotmail.com>
Date: Mon, 22 Jun 2009 22:32:49 CST Raw View
"Scott Meyers" <usenet@aristeia.com> wrote in message
news:U4SdnfTBL5UUcLLXnZ2dnUVZ_gOdnZ2d@posted.hevanet...
> Bo Persson wrote:
>> I wonder if we are not having a strong runner up for the "most vexing
>> parse" title here. :-)
>
> This isn't a parsing problem, it's a semantic one.
Perhaps it is a syntax problem.
I'm really starting to think that perhaps the collapseable reference syntax
should be rethought.
Lets look at the semantics again:
(A) T => int | T=int
(B) T => int& | T=int&
(C) T => int&& | T=int&&
(D) T& => int& | T=int
(E) T& => int& | T=int&
(F) T& => int&& | T=int&&
(G) T&& => int&& | T=int
(H) T&& => int& | T=int&
(I) T&& => int&& | T=int&&
The line that generally causes confusion is H. Perhaps a T&& should never
collapse, and instead have T&&& that does the perfect forwarding
style-collapsing shown.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Fri, 26 Jun 2009 23:10:29 CST Raw View
restor wrote:
> This declaration
> void fun( Animal & animal );
> obviously to everyone (expert and novice alike) indicates that we will
> modify an _object_ of type Animal using alias "animal".
>
> This declaration
> template< typename T >
> void fun( T & obj );
> *does not* mean that we will modify any object that we happen to
> deduce by alias "obj". This is because T may not be an _object_.
> "T" might be a reference, or may be void, or may be a literal. (and
> this is a problem in C++98 too).
T might be a reference, although this would be uncommon (I don't believe it
would never be deduced to be such), but, due to the reference-collapsing rule in
C++03, that would make obj a reference, too, and we could modify the referred-to
object via obj. We don't have to worry about literals, because they are rvalues
and won't bind to non-const (lvalue) references. I have no idea what would
happen if we tried to invoke f<void>, and I'm too lazy to check right now :-)
> If you really want to say "take object T by l-ref" you have to say
> "object" explicitly:
> template< ObjectType T >
> void fun( T & obj );
That depends, I suppose, on what you mean by "object". (The Standard just calls
it a chunk of memory, which is not, in general, terribly helpful.) Given the
unconstrained template above ("typename T") I don't think there is any situation
in which you can find yourself executing the body of fun without obj referring
to a modifiable lvalue. If I'm correct, you do *not* have to use the
constrained form above.
> You only want to catch temporary objects (to apply move)? It is
> simple:
> template< ObjectType T >
> void fun( T && obj );
>
> That's it. You do not want to type
> template< typename T >
> void fun( T & obj );
> for at least two reasons:
> First, it is an unconstrained template, and all the story about error
> messages and early error detection begins...
Yes, well, for the time being, those stories remain just that: stories. I'll
withhold judgment until we see what real compilers do. I've played around a
tiny bit with concept gcc, and the diagnostics I've seen from it, while mildly
better than what I get from contemporary compilers piped through stlfilt, are
far from the paragons of clarity I've seen claimed for the concept-enabled
compilers of the future.
> Second, your template catches references, void and you are risking
> producing references to void or references to references, which you
> know will be trouble.
References to references, at least when produced through type deduction,
collapse to references and are not problematic. I suspect that references to
void won't compile, and something that's meaningless and that does not compile
is, to me, not really something I'd call trouble. (Trouble is meaningless
things that do compile or meaningful things that compile in ways you don't
expect.)
> template< ObjectType T >
> and you should always prefer it, unless you are either an expert or a
> masochist.
Under what conditions? Literals aren't objects, and if I'm writing a template
that has lvalue-references-to-const, I certainly want literals to be able to
bind. I also want them to be able to bind to rvalue references for perfect
forwarding. So when is the use of ObjectType more appropriate than an
unconstrained parameter?
> 1. Templates are checked ass soon as they are defined and error
> messages are human-parse-able
I remain skeptical. Show me a compiler that produces these human-parseable
messages.
> 2. You get the "ObjectType T" idiom which eliminates lots of subtle
> mistakes.
What mistakes would compile with unconstrained templates that will not compile
using ObjectType?
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: restor <akrzemi1@gmail.com>
Date: Mon, 29 Jun 2009 10:44:14 CST Raw View
> > This declaration
> > void fun( Animal & animal );
> > obviously to everyone (expert and novice alike) indicates that we will
> > modify an _object_ of type Animal using alias "animal".
> >
> > This declaration
> > template< typename T >
> > void fun( T & obj );
> > *does not* mean that we will modify any object that we happen to
> > deduce by alias "obj". This is because T may not be an _object_.
> > "T" might be a reference, or may be void, or may be a literal. (and
> > this is a problem in C++98 too).
>
> T might be a reference, although this would be uncommon (I don't believe it
> would never be deduced to be such), but, due to the reference-collapsing rule in
> C++03, that would make obj a reference, too, and we could modify the referred-to
> object via obj. We don't have to worry about literals, because they are rvalues
> and won't bind to non-const (lvalue) references. I have no idea what would
> happen if we tried to invoke f<void>, and I'm too lazy to check right now :-)
First, to settle some assumptions, I am trying to look at it from
mediocre programmer's perspective. When I hear something like "it is
ok because <<reference collapsing>> will be applied" it is bad
already. Novice programmer (that happens to know templates and
references) doesn't mind he will just type the above and it will most
probably work as intended. The problem is when I start to thing more
deeply about it. First they tell me that I cannot bind a reference to
a reference because it is an error, and now (in templates) they tell
me taht it is ok because of some "reference collapsing". Why the
difference? Now whenever I use a reference I will have to consider if
"reference collapsing" applies and if it changes anything. Another
thing to bother my mind. I consider this a problem in C++98. Well, it
follows the general rule "make simple usages intuitive, and complex
usages managable", but is still something strange and scarry. So I
would expect C++0x to provide some improvement in this area.
We don't have to worry about literals or void because the compiler
will detect the problem sooner or later and will report an error. You
are right, but I would like to be able to _express_ my intentions
clearly in the code, so taht anyone can easily see what I meant
without even compiling the code. I find it similar to putting
assetions in my code even if I know the program is always run in
NDEBUG mode or similar to axioms. They are not (or not necessarily)
for the compiler, they are for other programmers.
> > If you really want to say "take object T by l-ref" you have to say
> > "object" explicitly:
> > template< ObjectType T >
> > void fun( T & obj );
>
> That depends, I suppose, on what you mean by "object". (The Standard just calls
> it a chunk of memory, which is not, in general, terribly helpful.) Given the
> unconstrained template above ("typename T") I don't think there is any situation
> in which you can find yourself executing the body of fun without obj referring
> to a modifiable lvalue. If I'm correct, you do *not* have to use the
> constrained form above.
You are right. Yet, I still find using the latter preferable due to
(1) "clarity" I tried to explain above, (2) avoidance of ever thinking
about "reference collapsing", (3) in C++0x this avoid the whole
explanations and surprises about "r-val and l-val reference
collapsing", (4) using a constrained template rather than an
unconstrained on as a rule.
> > You only want to catch temporary objects (to apply move)? It is
> > simple:
> > template< ObjectType T >
> > void fun( T && obj );
> >
> > That's it. You do not want to type
> > template< typename T >
> > void fun( T & obj );
> > for at least two reasons:
> > First, it is an unconstrained template, and all the story about error
> > messages and early error detection begins...
>
> Yes, well, for the time being, those stories remain just that: stories. I'll
> withhold judgment until we see what real compilers do. I've played around a
> tiny bit with concept gcc, and the diagnostics I've seen from it, while mildly
> better than what I get from contemporary compilers piped through stlfilt, are
> far from the paragons of clarity I've seen claimed for the concept-enabled
> compilers of the future.
I am not sure I understend your point here. In general the C++
standard does not require that compilation error messages be "clear".
But there is one obvious improvement that is enabled with concepts (if
they make it to C++0x). Today when I write a library with a function
template, it's body is never checked for invalid operations due to 2-
phase lookup and no assumptions on "typename T" parameter. Thus, it is
only the user of my function that will get compilation errors in (of)
my function. Any diagnostic on my part, readable or not, is better
than none.
> > Second, your template catches references, void and you are risking
> > producing references to void or references to references, which you
> > know will be trouble.
>
> References to references, at least when produced through type deduction,
> collapse to references and are not problematic. I suspect that references to
> void won't compile, and something that's meaningless and that does not compile
> is, to me, not really something I'd call trouble. (Trouble is meaningless
> things that do compile or meaningful things that compile in ways you don't
> expect.)
Agreed on your definition of "trouble". However, reference collapsing
may still fall into the category "meaningless things that do compile"
for someone that is informed that references to references are
illegal.
> > template< ObjectType T >
> > and you should always prefer it, unless you are either an expert or a
> > masochist.
>
> Under what conditions? Literals aren't objects, and if I'm writing a template
> that has lvalue-references-to-const, I certainly want literals to be able to
> bind.
> I also want them to be able to bind to rvalue references for perfect
> forwarding. So when is the use of ObjectType more appropriate than an
> unconstrained parameter?
You are right. However I find it to be a problem with my choice of a
concept rather than constrained vs. unconstrained template choice.
Perhaps concept ReferentType would do what I intended. If you (as a
library writer) want to implement perfect forwarding, then this would
be an "advanced" usage of templates which would require the knowlege
of C++ tricks/gotchas like reference collapsing, and unconstrained (or
less constrained) templates, just as you say.
A non-advanced usage, in contrast, would be a function template that
modifies an object. Or a query on an object from your example:
template< ReferentType T >
bool predicate( T const& obj );
I may be incorrect, though. I just assumed std::ReferentType is any
type that we can have a reference to. I am not that familiar with
concepts wording.
> > 1. Templates are checked ass soon as they are defined and error
> > messages are human-parse-able
>
> I remain skeptical. Show me a compiler that produces these human-parseable
> messages.
Commented above.
> > 2. You get the "ObjectType T" idiom which eliminates lots of subtle
> > mistakes.
>
> What mistakes would compile with unconstrained templates that will not compile
> using ObjectType?
First, the whole problem and surprises of reference collapsing (the
word "mistake" may have been inappropriate); second, the clarity of
intentions; third "improved error messages" from a constrained
template. You don't seem to be buying the "improved error messages"
argument. I am not the right person to argue about that, as I haven't
even used ConceptGCC. ConceptGCC was not conformant to the concept
proposal (current at that time), not to mention conformance to the
current (stil instable?) wording version.
Let me just add a general observation here. Reference collapsing (in c+
+98) is strange, but acceptable, and the rule & + & == & is relatively
easily accepted. In C++0x with two ref types rules
& + & == & and && + && == &&
are also "natural. For the other two we will have a problem whatever
the result is. Assuming that getting rid of reference collapsing is
irrealistic, whatever the rule for & + && is, it is bizzare, so there
is no point in "critisizing" the rule & + && == &. It is the reference
collapsing problem that ought to be "critisized", if the critisizm is
required.
Regards,
&rzej
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Tue, 30 Jun 2009 09:30:36 CST Raw View
restor wrote:
> I am not sure I understend your point here. In general the C++
> standard does not require that compilation error messages be "clear".
> But there is one obvious improvement that is enabled with concepts (if
> they make it to C++0x). Today when I write a library with a function
> template, it's body is never checked for invalid operations due to 2-
> phase lookup and no assumptions on "typename T" parameter. Thus, it is
> only the user of my function that will get compilation errors in (of)
> my function. Any diagnostic on my part, readable or not, is better
> than none.
Okay, fair enough, although I'm still skeptical that this is a problem
in practice. I'd certainly hope that using operations that are not
guaranteed to be available is an uncommon problem, because those are
the kinds of things I'd expect any decent library to expose during
testing. However, I'm not a library writer.
> Agreed on your definition of "trouble". However, reference collapsing
> may still fall into the category "meaningless things that do compile"
> for someone that is informed that references to references are
> illegal.
But reference collapsing is part of C++98, and, as far as I know,
there will be no changes to those rules. Or is there something that
says that in a constrained template, the C++98 reference-collapsing
rules don't apply?
> You are right. However I find it to be a problem with my choice of a
> concept rather than constrained vs. unconstrained template choice.
> Perhaps concept ReferentType would do what I intended. If you (as a
> library writer) want to implement perfect forwarding, then this would
> be an "advanced" usage of templates which would require the knowlege
> of C++ tricks/gotchas like reference collapsing, and unconstrained (or
> less constrained) templates, just as you say.
I'm not versed enough in this to know for sure, buy my guess is that
if you want to get *perfect* forwarding, you'll need to use an
unconstrained template. After all, the whole idea is that you want to
forward whatever you get without looking at it, much less constraining
it.
Anyway, I think we are in agreement in principle. If you can express
your intent as a library writer more precisely using a constrained
template, by all means, use a constrained template. My fundamental
objection to what you wrote before was your assertion that one *must*
use constrained templates to prevent "bad" code from compiling.
> Let me just add a general observation here. Reference collapsing (in c+
> +98) is strange, but acceptable, and the rule & + & == & is relatively
> easily accepted.
I'd go further. Reference collapsing in C++98 was added, because the
"reference to reference problem" surprised almost everybody. It was
an unanticipated artifact of the template instantiation rules, and the
reference collapsing rule simply made the artifact go away.
> In C++0x with two ref types rules
> & + & == & and && + && == &&
> are also "natural.
My concern about the handling of rrefs in C++0x templates has nothing
to do with reference collapsing. In C++98, if you see a template like
this,
template<typename T> void f(T& x);
you know that all instantiated functions take their parameter by
reference. In C++0x, if you see a template like this,
template<typename T> void f(T&& x);
you do NOT know that all instantiations take an rref parameter. In
essence, C++0x has defined "T&&" in a template to be a new kind of
template parameter. This is a fundamentally new idea, and not one I
have any enthusiasm for. This has nothing whatsoever to do with
reference collapsing. It's a completely independent thing.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Greg Herlihy <greghe@mac.com>
Date: Mon, 8 Jun 2009 11:31:30 CST Raw View
On Jun 8, 12:16 am, Scott Meyers <use...@aristeia.com> wrote:
> Joe Smith wrote:
> > The collapsing rules keep confusing people. Perhaps because there does not
> > seem to be any good mnemonic for it.
>
> > if T=int:
> > (A) T => int
> > (B) T& => int&
> > (C) T&& => int&&
>
> > if T=int&
> > (D) T => int&
> > (E) T& => int&
> > (F) T&& => int&
>
> For me, the problem is the notion that a function template declaring an
> rvalue-ref parameter might generate a function taking an lvalue-ref parameter.
> So we see T&& in the source code, and sometimes that yields int&& in a generated
> function and sometimes it generates int&. This breaks new ground compared to
> C++03, and I find it just plain bizarre. The best way I've found to think of it
> is that "T&&" is now a funny kind of variable, the type of which will be deduced
> during template argument deduction along with T.
A C++0x programmer will not have to worry about reference type
collapsing - for the simple reason that that programmer will not be
declaring (or, at least, will have no good reason to declare) a T& or
T&& template type parameter in the first place.
The "proper" way to specify an lvalue- or rvalue-reference type
parameter in a C++0x template - is to constrain its type:
template <class T>
requires RvalueReference<T>
void f( T t)
{ .. . }
In this way, the programmer can be sure that the f() function template
above will be called with an rvalue-reference type only as its
argument - and that f() will not be called with an argument of some
other type - thanks to some obscure rule involving reference type
collapsing.
Greg
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Mon, 8 Jun 2009 13:21:11 CST Raw View
Greg Herlihy wrote:
> The "proper" way to specify an lvalue- or rvalue-reference type
> parameter in a C++0x template - is to constrain its type:
>
> template <class T>
> requires RvalueReference<T>
> void f( T t)
> { .. . }
>
> In this way, the programmer can be sure that the f() function template
> above will be called with an rvalue-reference type only as its
> argument - and that f() will not be called with an argument of some
> other type - thanks to some obscure rule involving reference type
> collapsing.
Okay, so now that lvalues are not permitted to bind to rrefs, the proper way to
declare f is presumably to overload on reference type:
template<class T>
requires RvalueReference<T>
void f(T t);
template<class T>
requires LvalueReference<T>
void f(T t);
Let's suppose that f should not modify t. Then we presumably have:
template<class T>
requires RvalueReference<T>
void f(T t);
template<class T>
requires LvalueReference<T>
void f(const T t); // const added
Is that right? If so, the second declaration will take some serious getting
used to by pretty much anybody who has written function templates in C++98,
because it looks, at first glance, like T is passed by value. It also reads as
if it accepts only lvalues, but that's not true: it will also bind to const
rvalues. (At least I assume it will. It would if the parameter were declared
as const T&.) Const rvalues are not terribly common now and in C++0x will be
downright gauche, but they do exist, and they won't bind to non-const rrefs,
which, from what I can tell, are the only kind that are supposed to exist.
I'm skeptical that you will get people who have in some cases written
template<class T>
void f(const T& t);
for a decade to switch to
template<class T>
requires LvalueReference<T>
void f(const T t);
because it looks funny, it's more verbose, it's inconsistent with 18 gazillion
lines of existing code, and it does not offer any apparent advantage over its
C++0x counterpart. That being the case, I'm guessing a fair number of people
will also write
template<class T>
void f(T&& t);
because it's shorter and it's more syntactically parallel with what they've
always written. Plus, in the short term, the concept syntax won't compile,
while the rref stuff might. (VC10 and gcc 4.4, for example, both support rrefs,
but neither supports concepts.)
Finally, C++ has a lousy track record of shielding its users from the need to
know about obscure language rules. As somebody who has spent close to two
decades on the front lines of trying to explain to people how to use C++0x, I
can tell you that comparatively few C++ developers are content with "just type
this, and don't trouble yourself with trying to understand what is going on."
And part of what's going on will involve dealing with the unnatural phenomenon
that is 14.9.2.1/3.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Tue, 9 Jun 2009 10:33:32 CST Raw View
On 8 Jun., 21:21, Scott Meyers <use...@aristeia.com> wrote:
[..]
> Let's suppose that f should not modify t. Then we presumably have:
>
> template<class T>
> requires RvalueReference<T>
> void f(T t);
>
> template<class T>
> requires LvalueReference<T>
> void f(const T t); // const added
>
> Is that right?
This is not ill-formed, but the const qualifier is ignored, see
[dcl.ref]/1:
"[..] Cv-qualified references are ill-formed except when the
cv-qualifiers are introduced through the use of a typedef
(7.1.3) or of a template type argument (14.4), in which
case the cv-qualifiers are ignored. [ Example:
typedef int& A;
const A aref = 3; // ill-formed; non-const reference initialized with
rvalue
The type of aref is reference to int , not const reference to int .
end example ][..]"
> I'm skeptical that you will get people who have in some cases written
>
> template<class T>
> void f(const T& t);
>
> for a decade to switch to
>
> template<class T>
> requires LvalueReference<T>
> void f(const T t);
>
> because it looks funny, it's more verbose, it's inconsistent with 18 gazillion
> lines of existing code, and it does not offer any apparent advantage over its
> C++0x counterpart.
Fortunately that will not happen.
> That being the case, I'm guessing a fair number of people
> will also write
>
> template<class T>
> void f(T&& t);
>
> because it's shorter and it's more syntactically parallel with what they've
> always written. Plus, in the short term, the concept syntax won't compile,
> while the rref stuff might. (VC10 and gcc 4.4, for example, both support rrefs,
> but neither supports concepts.)
In constrained C++ you just write
template<class T>
requires !LvalueReference<T>
void f(T&& t);
to eliminate lvalues. This takes again advantage of the
deduction rules. If an lvalue is provided, T is deduced as T'&
which is an lvalue and which now will remove the overload
from the set (if existing). Note that the suggested form
of writing
template<class T>
requires LvalueReference<T>
void f(T t);
will only accept the overload, if T was *explicitly* provided
like so
int i;
f<int&>(i);
if you write instead
int& i = ...;
f(i);
T will be deduced is int, a non-reference type, so the
"by-value" argument is of little use for discriminating
references.
Greetings from Bremen,
Daniel Kr gler
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SG <s.gesemann@gmail.com>
Date: Tue, 9 Jun 2009 10:32:03 CST Raw View
On 8 Jun., 21:21, Scott Meyers <use...@aristeia.com> wrote:
> Greg Herlihy wrote:
> > The "proper" way to specify an lvalue- or rvalue-reference type
> > parameter in a C++0x template - is to constrain its type:
>
> > template <class T>
> > requires RvalueReference<T>
> > void f( T t)
> > { .. . }
>
> > In this way, the programmer can be sure that the f() function template
> > above will be called with an rvalue-reference type only as its
> > argument - and that f() will not be called with an argument of some
> > other type - thanks to some obscure rule involving reference type
> > collapsing.
>
> Okay, so now that lvalues are not permitted to bind to rrefs, the proper way to
> declare f is presumably to overload on reference type:
>
> template<class T>
> requires RvalueReference<T>
> void f(T t);
>
> template<class T>
> requires LvalueReference<T>
> void f(T t);
I don't agree with this... although it really depends on what you
actually want. Since there's no ampersand in the function parameter
list and template parameter deduction won't yield reference types in
this case the only way to invoke these functions is by explicitly
stating what T is:
int a = 23;
f<int&> (a);
f<int&&>(a+3);
If you're okay with that, it's fine. If you want template type
parameter deduction to select the right function for you you have to
do something different. In any case, a requires clause does NOT affect
template parameter deduction -- not as far as I know.
> Let's suppose that f should not modify t. Then we presumably have:
>
> template<class T>
> requires RvalueReference<T>
> void f(T t);
>
> template<class T>
> requires LvalueReference<T>
> void f(const T t); // const added
>
> Is that right?
In this case, if you want the 2nd function to be called you have to
explicitly specify T to be an lvalue reference. The const
qualification is ignored because references are inherently const. So,
the 2nd function with T=lvalue reference takes a reference to a
possibly non-const object as parameter -- not exactly what you want.
> I'm skeptical that you will get people who have in some cases written
>
> template<class T>
> void f(const T& t);
>
> for a decade to switch to
>
> template<class T>
> requires LvalueReference<T>
> void f(const T t);
>
> because it looks funny, it's more verbose, it's inconsistent with 18 gazillion
> lines of existing code, and it does not offer any apparent advantage over its
> C++0x counterpart.
You're right. There's no reason to do that. If you want it to work
magically (template argument deduction) and want to take advantage of
rvalues you can simply reuse the "const T&" code
template<class T>
void f(T const&);
and supply an overload like this for rvalues
template<class T> requires !LvalueReference<T>
void f(T &&);
where !LvalueReference<T> specifically disables the function for
lvalue arguments. This is necessary to work around reference
collapsing and the parameter deduction rules that enable perfect
forwarding.
Now, if you want to combine perfect forwarding with constrains for the
types of referred object parameters it's going to get ugly.
std::make_pair (N2857) is an example of how it's NOT done. The final
version of std::make_pair is going to be interesting if it really
manages to combine
1. perfect forwarding to avoid unnecessary copies
2. proper constraining of the parameter types via concepts
3. unpacking of references from reference_wrappers
I think it's possible ... but certainly not trivial. :-)
> That being the case, I'm guessing a fair number of people
> will also write
>
> template<class T>
> void f(T&& t);
...which is really a dangerous mistake to make in this case. This one
is a more attractive overload for non-const lvalues than the other
function template due to the const conversion. If this function
contains a std::move(t) you'll be destroying a non-const lvalue
argument. One really has to think twice before writing "T&&" where T
can be deduced by the compiler.
> Finally, C++ has a lousy track record of shielding its users from the need to
> know about obscure language rules. As somebody who has spent close to two
> decades on the front lines of trying to explain to people how to use C++0x, I
> can tell you that comparatively few C++ developers are content with "just type
> this, and don't trouble yourself with trying to understand what is going on."
Understandably.
> And part of what's going on will involve dealing with the unnatural phenomenon
> that is 14.9.2.1/3.
Likely.
Cheers!
SG
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Greg Herlihy <greghe@mac.com>
Date: Tue, 9 Jun 2009 10:32:27 CST Raw View
On Jun 8, 12:21 pm, Scott Meyers <use...@aristeia.com> wrote:
>
> Okay, so now that lvalues are not permitted to bind to rrefs, the proper way to
> declare f is presumably to overload on reference type:
>
> template<class T>
> requires RvalueReference<T>
> void f(T t);
>
> template<class T>
> requires LvalueReference<T>
> void f(T t);
>
> Let's suppose that f should not modify t. Then we presumably have:
>
> template<class T>
> requires RvalueReference<T>
> void f(T t);
>
> template<class T>
> requires LvalueReference<T>
> void f(const T t); // const added
>
> Is that right?
These concept declarations look OK to me, but I have not tried
compiling them with the conceptg++ compiler.
>If so, the second declaration will take some serious getting
> used to by pretty much anybody who has written function templates in C++98,
> because it looks, at first glance, like T is passed by value. It also reads as
> if it accepts only lvalues, but that's not true:
Yes, these are valid criticisms. And I can think of two ways to
improve f()'s constrained template declaration. The first would be to
add the "missing" ampersands to both declarations:
template<class T>
requires RvalueReference<T>
void f(T&& t);
template<class T>
requires LvalueReference<T>
void f(T& t);
In this case, the rules for reference collapsing (which just one post
ago I had dismissed as obscure and useless detail) turn out to be
quite useful. Reference collapsing allows us to add the expected
number of ampersands to both f() declarations - and to do so without
affecting T's deduced type. Yet, despite the apparent improvement to f
()'s declaration by this change - I would still not recommend making
this improvement.
The problem with out beautification of f() is that the declaration
itself, becomes misleading. Although the ampersands (that we added)
appear to belong to f()'s parameter type specification - we know that
they do nothing at all. And that the declaration of f() would
essentially be the same without them.
Therefore, I would recommend another way to improve f()' declaration:
that is, to build f()'s concept declaration from the "ground up"
instead of from the top down. In other words - instead of making sure
that "T" is a reference type, we make sure that "T" in "T&&" and "T&"
is -not- a reference type.
For example:
template <class T>
requires !LvalueReference<T> && !RvalueReference<T>
void f( T& ) // matches an lvalue reference argument
template <class T>
requires !LvalueReference<T> && !RvalueReference<T>
void f( T&& ) // matches an lvalue reference argument
Now, the ampersands are not only useufly in identifying the kind of
parameter type - they are actually needed.
I should note that the above constrained template declarations show
(for demonstration purposes) how negative concept maps and boolean
operators can be used to constrain "T"'s type. In reality, these
declarations do not have to be so elaborate. Constraining T with, say,
"ObjectType&" would make f()'s declaration much more compact.
> I'm skeptical that you will get people who have in some cases written
>
> template<class T>
> void f(const T& t);
>
> for a decade to switch to
>
> template<class T>
> requires LvalueReference<T>
> void f(const T t);
I agree. That is why I now realize that my original suggestion to use
LvalueReference to constrain f()'s parameter type was not a very good
suggestion at all. (sorry for the bad steer, but hey - you get what
you pay for in this forum :-)). Anyway, a much better suggestion would
have been the one I just made, that is write the constrained template
in a more ground-up, "layered" fashion. For example:
template <class T>
requires ObjectType<T>
void f( const T& t)
In this example, "T" is constrained to non reference types, in order
that t's "T&" type - will match lvalue reference type arguments only.
There are no doubt other ways to improve f()'s declaration. In fact,
because concepts are so new - and because they have so much expressive
power - I suspect that many of the best ways to use concepts (and many
of the best uses of concepts) have yet to be discovered.
Greg
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Tue, 9 Jun 2009 18:05:50 CST Raw View
Daniel Kr gler wrote:
> In constrained C++ you just write
>
> template<class T>
> requires !LvalueReference<T>
> void f(T&& t);
>
> to eliminate lvalues.
I was under the impression that with the new rules for rref binding, lvalues
never bind to rrefs. That being the case, I don't see why you'd need the
constraint to exclude lvalues above.
How does the above code differ behaviorally from this?
template<class T>
void f(T&& t);
Thanks,
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Tue, 9 Jun 2009 18:05:22 CST Raw View
SG wrote:
> You're right. There's no reason to do that. If you want it to work
> magically (template argument deduction) and want to take advantage of
> rvalues you can simply reuse the "const T&" code
>
> template<class T>
> void f(T const&);
>
> and supply an overload like this for rvalues
>
> template<class T> requires !LvalueReference<T>
> void f(T &&);
>
> where !LvalueReference<T> specifically disables the function for
> lvalue arguments. This is necessary to work around reference
> collapsing and the parameter deduction rules that enable perfect
> forwarding.
Color me confused. Again. Let's go back to basics. Suppose I want to
move-enable a copyable class. I assume this is still the right thing to do, yes?
class Widget {
public
Widget(const Widget&); // copy
Widget(Widget&&); // move from rvalue
Widget& operator=(const Widget&); // copy
Widget& operator=(Widget&&); // move from rvalue
};
If this is correct, I assume that the obvious transformation into a template
class is also the correct thing to do:
template<class T>
class Widget {
public
Widget(const Widget&); // copy
Widget(Widget&&); // move from rvalue
Widget& operator=(const Widget&); // copy
Widget& operator=(Widget&&); // move from rvalue
};
Note that the member functions of this template are themselves templates.
Now I want to write a move-enabled free function for types that may support only
copying. Based on my Widget class, I'd assume I do this:
void f(const Widget&); // copy
void f(Widget&&); // move from rvalue
Right? If so, here's the obvious templatization:
template<class T> void f(const T&); // copy
template<class T> void f(T&&); // move from rvalue
Is this correct? If so, under what conditions do I need to mess with the
!LvalueReference<T> constraint?
Thanks,
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SG <s.gesemann@gmail.com>
Date: Wed, 10 Jun 2009 11:47:38 CST Raw View
On 10 Jun., 02:05, Scott Meyers <use...@aristeia.com> wrote:
> Color me confused. Again. Let's go back to basics. Suppose I want to
Just in case you havn't read it: Here's an introduction to rvalue
references that is more easily digestable than the current C++0x
draft:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
Also, Dave's "Rvalue References 101" is interesting. But it seems to
focus on move semantics:
https://www.boostpro.com/trac/wiki/BoostCon09/RValue101
> move-enable a copyable class. I assume this is still the right thing to do, yes?
>
> class Widget {
> public
> Widget(const Widget&); // copy
> Widget(Widget&&); // move from rvalue
> Widget& operator=(const Widget&); // copy
> Widget& operator=(Widget&&); // move from rvalue
> };
Fine.
> If this is correct, I assume that the obvious transformation into a template
> class is also the correct thing to do:
>
> template<class T>
> class Widget {
> public
> Widget(const Widget&); // copy
> Widget(Widget&&); // move from rvalue
> Widget& operator=(const Widget&); // copy
> Widget& operator=(Widget&&); // move from rvalue
> };
>
> Note that the member functions of this template are themselves templates.
Well, they are not "member templates" in C++ terminology and they
don't introduce a new template parameter that the compiler can deduce
from a function call. So, this is fine.
> Now I want to write a move-enabled free function for types that may support only
> copying. Based on my Widget class, I'd assume I do this:
>
> void f(const Widget&); // copy
> void f(Widget&&); // move from rvalue
Also fine.
> Right? If so, here's the obvious templatization:
>
> template<class T> void f(const T&); // copy
> template<class T> void f(T&&); // move from rvalue
>
> Is this correct? If so, under what conditions do I need to mess with the
> !LvalueReference<T> constraint?
Here, you walked into the 14.9.2.1/3 (N2857, page 376) "gotcha":
"If P is a cv-qualified type, the top level cv-qualifiers of P s type
are ignored for type deduction. If P is a reference type, the type
referred to by P is used for type deduction. If P is of the form T&&,
where T is a template parameter, and the argument is an lvalue, the
type A& is used in place of A for type deduction."
In your case P is T&& while T can be deduced from the function call.
If you invoke f with an lvalue argument, template parameter deduction
will make T an lvalue reference which -- due to reference collapsing
-- makes your function take an lvalue reference parameter. This rule
allows "perfect forwarding" (see also N1385, one of the early
proposals dealing with perfect forwarding). Here's another example
w.r.t. these rules:
void f(T&&);
void main() {
int a = 23;
f(a); // -> deduced T=int& --> T&& = int&
f(a+1); // -> deduced T=int --> T&& = int&&
}
So, in order to circumvent this deduction rule and remove this
function from the overload set when the argument is an lvalue you need
to apply some kind of SFINAE or use a requires clause.
There's one thing I forgot to mention but Daniel mentioned it: By
using a requires clause like !LvalueReference<T> you turn your
unconstrained template into a constrained one which makes the compiler
apply some more type-checking to the body of the function template.
Anything involving a dependent type that you write in a constrained
context that is not backed up by some requirement clauses is flagged
by the compiler:
template<typename T> requires !LvalueReference<T>
void foobar() {
T x; // <-- Compiler will complain because DefaultConstructible
// is not part of the function template's requirements.
late_check { // enforces C++98-like unconstrained context
T x; // will work
}
}
In case you have not seen Doug Gregor's talk on concepts and you're
interested, check out
http://video.google.com/videoplay?docid=-1790714981047186825
It's not 100% up to date, though ("where clause"<->"requires clause").
Cheers!
SG
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: joshuamaurice@gmail.com
Date: Wed, 10 Jun 2009 11:47:17 CST Raw View
On Jun 9, 5:05 pm, Scott Meyers <use...@aristeia.com> wrote:
> SG wrote:
> > You're right. There's no reason to do that. If you want it to work
> > magically (template argument deduction) and want to take advantage of
> > rvalues you can simply reuse the "const T&" code
>
> > template<class T>
> > void f(T const&);
>
> > and supply an overload like this for rvalues
>
> > template<class T> requires !LvalueReference<T>
> > void f(T &&);
>
> > where !LvalueReference<T> specifically disables the function for
> > lvalue arguments. This is necessary to work around reference
> > collapsing and the parameter deduction rules that enable perfect
> > forwarding.
>
> Color me confused. Again. Let's go back to basics. Suppose I want to
> move-enable a copyable class. I assume this is still the right thing to do, yes?
>
> class Widget {
> public
> Widget(const Widget&); // copy
> Widget(Widget&&); // move from rvalue
> Widget& operator=(const Widget&); // copy
> Widget& operator=(Widget&&); // move from rvalue
> };
>
> If this is correct, I assume that the obvious transformation into a template
> class is also the correct thing to do:
>
> template<class T>
> class Widget {
> public
> Widget(const Widget&); // copy
> Widget(Widget&&); // move from rvalue
> Widget& operator=(const Widget&); // copy
> Widget& operator=(Widget&&); // move from rvalue
> };
>
> Note that the member functions of this template are themselves templates.
>
> Now I want to write a move-enabled free function for types that may support only
> copying. Based on my Widget class, I'd assume I do this:
>
> void f(const Widget&); // copy
> void f(Widget&&); // move from rvalue
>
> Right? If so, here's the obvious templatization:
>
> template<class T> void f(const T&); // copy
> template<class T> void f(T&&); // move from rvalue
>
> Is this correct? If so, under what conditions do I need to mess with the
> !LvalueReference<T> constraint?
Now, I am in a little over my head here (I really need to read up on
this more), but the impression I always received was that the intent
(which may not mirror reality) is the following:
The creator of a class needs to decide whether or not to make a class
be move constructable. Ex:
class foo
{
public:
foo() {} //default constructable
foo(const foo& ) {} //copy constructable
foo(foo&& ) {} //move constructable
};
The creator of a class decides what semantics to support by defining
the appropriate functions.
I believe that most users of most classes will be agnostic as to
whether the class is "move constructable and copy constructable" or
just copy constructable. (A move constructable and not copy
constructable class will be the exception, not the rule, and may be
exempt from these guidelines. I need more time to think on that one.)
As I see it, nearly all rvalue references should be the parameters of
move constructors only. The rest of the code should be written as it
was in C++03. Specifically:
void foo(T );
You should pass by value when the function needs a copy which outlives
the call (or for reading small objects). This may be a copy or a move,
depending on what the class supports and if the argument is an rvalue
or lvalue. The caller can force an rvalue with std::move if the caller
knows the source does not need to outlive the call, which should be
harmless if the class is copy constructable and not move
constructable.
void foo(T& );
You should pass by non-const lvalue reference when you need to modify
the argument.
void foo(const T& );
You should pass by const lvalue reference when you need to read the
argument and don't need a copy. (For small objects, possibly instead
pass by value in this case.)
Specifically, why would you ever define two functions as such:
> void f(const Widget&); // copy
> void f(Widget&&); // move from rvalue
Are you suggesting that they have the same behavior? That's where I
find fault. Either the logic of f
1- needs a copy to outlive the call, in which case it should just pass
by value.
2- needs to modify the argument, in which case it should pass by non-
const lvalue reference.
3- needs just read access to the argument, in which case it should
pass by const lvalue reference.
I don't see the need to ever define such overloads for f, one which
copies its argument, and one which only allows move construction from
its argument. (Apart from defining move constructors and copy
constructors, that is.)
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Wed, 10 Jun 2009 11:46:36 CST Raw View
Scott Meyers wrote:
> SG wrote:
> Right? If so, here's the obvious templatization:
>
> template<class T> void f(const T&); // copy
> template<class T> void f(T&&); // move from rvalue
>
> Is this correct? If so, under what conditions do I need to mess with the
> !LvalueReference<T> constraint?
... this is another argument in favor of removing reference collapsing
rules (see my previous post).
Apparently, template<class T> void f(T&&) can be invoked with a
non-const lvalue-reference. IMHO, this is in contradiction
with "lvalue cannot bind to rvalue-reference". Different behavior
with templates only brings confusion.
--
Dragan
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Wed, 10 Jun 2009 11:46:05 CST Raw View
Scott Meyers wrote:
> Daniel Kr gler wrote:
>> In constrained C++ you just write
>>
>> template<class T>
>> requires !LvalueReference<T>
>> void f(T&& t);
>>
>> to eliminate lvalues.
>
> I was under the impression that with the new rules for rref binding,
> lvalues
> never bind to rrefs. That being the case, I don't see why you'd need the
> constraint to exclude lvalues above.
>
> How does the above code differ behaviorally from this?
>
> template<class T>
> void f(T&& t);
Foo foo;
f(foo);
With T = Foo &, the first one fails, the second one works
because of collapsing rules.
My instinct tells me that reference collapsing rules should be
terminated now that lvalues don't bind to rvalue-references.
But, for now, only instinct. I'll give it more thought...
Anyone else thinks the same?
--
Dragan
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Bo Persson" <bop@gmb.dk>
Date: Wed, 10 Jun 2009 11:45:33 CST Raw View
Scott Meyers wrote:
> Daniel Kr gler wrote:
>> In constrained C++ you just write
>>
>> template<class T>
>> requires !LvalueReference<T>
>> void f(T&& t);
>>
>> to eliminate lvalues.
>
> I was under the impression that with the new rules for rref
> binding, lvalues never bind to rrefs. That being the case, I don't
> see why you'd need the constraint to exclude lvalues above.
>
> How does the above code differ behaviorally from this?
>
> template<class T>
> void f(T&& t);
>
The problem with templates is that if T actually is a reference, like
A&, you end up having a function
void f(A& t);
which of course can bind to an lvalue.
I wonder if we are not having a strong runner up for the "most vexing
parse" title here. :-)
Bo Persson
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: James Hopkin <tasjaevan@gmail.com>
Date: Wed, 10 Jun 2009 17:07:12 CST Raw View
On Jun 10, 1:05 am, Scott Meyers <use...@aristeia.com> wrote:
>
> Now I want to write a move-enabled free function for types that may support only
> copying. Based on my Widget class, I'd assume I do this:
>
> void f(const Widget&); // copy
> void f(Widget&&); // move from rvalue
>
> Right? If so, here's the obvious templatization:
>
> template<class T> void f(const T&); // copy
> template<class T> void f(T&&); // move from rvalue
>
> Is this correct? If so, under what conditions do I need to mess with the
> !LvalueReference<T> constraint?
>
As far as I can tell, everything you've written is correct with no
gotchas. The rvalue constraint might be required in the unusual case
where a function requires a non-const rvalue and there's no lvalue
overload.
void f(int&&); // accepts only rvalue
The obvious templatisation (no, it doesn't look any better with the
British spelling ;-)):
template <class X> void f(X&&); // now accepts everything
would require a constraint to get equivalent behaviour.
James
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: =?ISO-8859-1?Q?Daniel_Kr=FCgler?= <daniel.kruegler@googlemail.com>
Date: Wed, 10 Jun 2009 17:07:35 CST Raw View
On Jun 10, 2:05 am, Scott Meyers <use...@aristeia.com> wrote:
> Daniel Kr gler wrote:
> > In constrained C++ you just write
>
> > template<class T>
> > requires !LvalueReference<T>
> > void f(T&& t);
>
> > to eliminate lvalues.
>
> I was under the impression that with the new rules for rref binding, lvalues
> never bind to rrefs. That being the case, I don't see why you'd need the
> constraint to exclude lvalues above.
Remember, above takes advantages of the "perfect forwarding"
declaration and we can rely on [temp.deduct.call]/3:
""If P is a reference type, the type referred to by P is used for
type deduction. If P is of the form T&&, where T is a template
parameter, and the argument is an lvalue, the type A& is used
in place of A for type deduction."
This essentially mean that every rvalue argument will cause
the function to deduce a non-reference T', but every lvalue
argumnet will cause the function to deduce an lvalue-reference
T'&. So, by applying the negative constraint
!LvalueReference<T> we exclude all argument types that
are lvalues.
> How does the above code differ behaviorally from this?
>
> template<class T>
> void f(T&& t);
This signature accepts both rvalues and lvalues. Note the slight
but important difference to
void g(U&&);
where U is some non-reference type. This non-template declaration
always ensures that only rvalues of U can bind to g. If U is let's say
a
typedef for a reference-type, the "normal" reference-collapsing rules
apply, so this is either a
void g(U'&)
if U was an lvalue-reference or
void g(U'&&)
if U was an rvalue-reference. This way of thinking is easily
extensible to template classes with member functions like
so (see example of [temp.arg.type]/4):
template <class T> class X {
void f(const T&);
void g(T&&);
};
X<int&> x1;
// X<int&>::f has the parameter type int&
// X<int&>::g has the parameter type int&
X<const int&&> x2;
// X<const int&&>::f has the parameter type const int&
// X<const int&&>::g has the parameter type const int&&
Greetings from Bremen,
Daniel Kr gler
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Thu, 11 Jun 2009 10:26:20 CST Raw View
SG wrote:
> On 10 Jun., 02:05, Scott Meyers <use...@aristeia.com> wrote:
>> template<class T> void f(const T&); // copy
>> template<class T> void f(T&&); // move from rvalue
>>
>> Is this correct? If so, under what conditions do I need to mess with the
>> !LvalueReference<T> constraint?
>
> Here, you walked into the 14.9.2.1/3 (N2857, page 376) "gotcha":
>
> "If P is a cv-qualified type, the top level cv-qualifiers of P s type
> are ignored for type deduction. If P is a reference type, the type
> referred to by P is used for type deduction. If P is of the form T&&,
> where T is a template parameter, and the argument is an lvalue, the
> type A& is used in place of A for type deduction."
>
> In your case P is T&& while T can be deduced from the function call.
> If you invoke f with an lvalue argument, template parameter deduction
> will make T an lvalue reference which -- due to reference collapsing
> -- makes your function take an lvalue reference parameter.
So if I invoke f on a modifiable lvalue of type Widget, the generated function
candidates would be
f(const Widget&);
f(Widget&);
and the call will resolve to the second one (generated from the template taking
an rref), due to the fact that the argument is modifiable (i.e., non-const).
Correct?
If so, then perhaps what I'm having trouble remembering is that a function
template declaring an rref parameter doesn't necessarily generate functions
taking rrefs. Which is actually quite sick, even if it does happen to do the
right thing for perfect forwarding.
Thanks for the clarification.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Thu, 11 Jun 2009 10:25:52 CST Raw View
Bo Persson wrote:
> I wonder if we are not having a strong runner up for the "most vexing
> parse" title here. :-)
This isn't a parsing problem, it's a semantic one. We really are in Wonderland
here. When we see T&& in a function, it means T&&. When we see T&& in a
function template generated from a class template, it means T&& (I think). When
we see T&& in a template for a free function, it might mean T&&, but it might
mean T&. It reminds me of const in C++0x, where nothing is const unless it's
declared to be, except for *this in a closure (generated from a lambda), which
is const unless it's declared not to be (a situation that cannot exist in C++98,
so they had to invent new syntax for it).
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Thu, 11 Jun 2009 11:47:30 CST Raw View
joshuamaurice@gmail.com wrote:
Specifically, why would you ever define two functions as such:
void f(const Widget&); // copy
void f(Widget&&); // move from rvalue
Are you suggesting that they have the same behavior? That's where I
find fault. Either the logic of f
1- needs a copy to outlive the call, in which case it should just pass
by value.
2- needs to modify the argument, in which case it should pass by non-
const lvalue reference.
3- needs just read access to the argument, in which case it should
pass by const lvalue reference.
This is an excellent analysis, thank you very much. I guess we need
to add a fourth case, however:
4- Needs to perfectly forward its argument to another function, in
which case it should pass by non-const rvalue reference.
Your fundamental point that there should be no need for the kind of
overloading I posted about seems sound to me.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SG <s.gesemann@gmail.com>
Date: Thu, 11 Jun 2009 11:48:20 CST Raw View
Bo Persson wrote:
> Scott Meyers wrote:
> > I was under the impression that with the new rules for rref
> > binding, lvalues never bind to rrefs.
This is still the case. But a "T&&" is not necessarily an rvalue
reference. It's an lvalue reference if T is an lvalue reference.
> I wonder if we are not having a strong runner up for the "most vexing
> parse" title here. :-)
Maybe this special template parameter deduction rule should be
disabled by default and only turned on with a special syntax.
Something like this perhaps:
template<typename & T> // <-- #1
void outer(T && x) {
inner(std::forward<T>(x);
}
The ampersand in #1 would explicitly enable the special template
parameter deduction rule for perfect forwarding. The more I think
about it the more I like it.
Cheers!
SG
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Thomas J. Gritzan" <phygon_antispam@gmx.de>
Date: Thu, 11 Jun 2009 12:28:15 CST Raw View
SG wrote:
> In your case P is T&& while T can be deduced from the function call.
> If you invoke f with an lvalue argument, template parameter deduction
> will make T an lvalue reference which -- due to reference collapsing
> -- makes your function take an lvalue reference parameter. This rule
> allows "perfect forwarding" (see also N1385, one of the early
> proposals dealing with perfect forwarding). Here's another example
> w.r.t. these rules:
>
> void f(T&&);
>
> void main() {
> int a = 23;
> f(a); // -> deduced T=int& --> T&& = int&
> f(a+1); // -> deduced T=int --> T&& = int&&
> }
Side question:
Since the auto keyword is specified in terms of template type deduction,
this will apply to 'auto', too, won't it?
So:
int main() // main returns int
{
int a = 42;
auto&& b = a; // -> b has type int&
auto&& c = a+1; // -> c has type int&&
}
Is this correct?
--
Thomas
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SG <s.gesemann@gmail.com>
Date: Thu, 11 Jun 2009 16:41:04 CST Raw View
On 11 Jun., 20:28, "Thomas J. Gritzan" <phygon_antis...@gmx.de> wrote:
> SG wrote:
>
> > void f(T&&);
> >
> > void main() {
> > int a = 23;
> > f(a); // -> deduced T=int& --> T&& = int&
> > f(a+1); // -> deduced T=int --> T&& = int&&
> > }
>
> Side question:
>
> Since the auto keyword is specified in terms of template type deduction,
> this will apply to 'auto', too, won't it?
Yes.
> So:
>
> int main() // main returns int
> {
> int a = 42;
>
> auto&& b = a; // -> b has type int&
> auto&& c = a+1; // -> c has type int&&
> }
>
> Is this correct?
Yes. IMHO, it's rather convenient. One example of where it is actually
used is the for-range loop where
for(#1 : #2) {
#3
}
is magically replaced with something like this
{
auto && __range = #2;
typedef remove_reference<decltype(__range)>::type _RangeT;
for (auto __begin = std::Range<_RangeT>::begin(__range),
__end = std::Range<_RangeT>::end(__range),
__start != __end; ++__begin)
{
#1 = *__begin;
#3
}
}
Note that #2 can be both, an lvalue (i.e. local vector) and an rvalue
(i.e. a vector returned by value by some function).
Cheers!
SG
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Space Cowboy <joshuamaurice@gmail.com>
Date: Fri, 12 Jun 2009 11:05:14 CST Raw View
On Jun 11, 10:47 am, Scott Meyers <use...@aristeia.com> wrote:
> joshuamaur...@gmail.com wrote:
>
> Specifically, why would you ever define two functions as such:
>
> void f(const Widget&); // copy
> void f(Widget&&); // move from rvalue
>
> Are you suggesting that they have the same behavior? That's where I
> find fault. Either the logic of f
> 1- needs a copy to outlive the call, in which case it should just pass
> by value.
> 2- needs to modify the argument, in which case it should pass by non-
> const lvalue reference.
> 3- needs just read access to the argument, in which case it should
> pass by const lvalue reference.
>
> This is an excellent analysis, thank you very much. I guess we need
> to add a fourth case, however:
>
> 4- Needs to perfectly forward its argument to another function, in
> which case it should pass by non-const rvalue reference.
>
> Your fundamental point that there should be no need for the kind of
> overloading I posted about seems sound to me.
Of course, we can still reason about the syntax rules, but there
shouldn't be a need to explain to someone when to use one and when to
use another. Instead just explain it away as being convoluted at
best.
However, I'm still not quite sure what you mean when you say
> 4- Needs to perfectly forward its argument to another function, in
> which case it should pass by non-const rvalue reference.
I'm sorry that I don't have ready access to a compiler which supports
the extensions, and half the websites googled lead to old descriptions
based on the old semantics where rvalue references can bind to
lvalues, so please forgive typos / missing headers / gross
inaccuracies.
You want to define a function which perfectly forwards its argument to
another function. Let's take the following as an example. Let's take a
class T which is default, copy, and move constructable.
class T
{
public:
T();
T(const T& );
T(T&& );
};
Let's take some functions foo, bar, and baz. I want to define foo to
call bar, and bar to call baz, passing along the argument each time.
foo and bar do not save a copy of the argument, and thus we should be
able to perfectly forward the argument without making deep copies.
void foo (T x);
void bar (T x);
void baz (T x);
int main()
{ T x;
//has to make a deep copy.
foo(x); //line 1
/* will do a shallow move, not a deep copy, if
T supports move (which it does)
and foo's argument is by value (which it does)
*/
foo(std::move(x)); //line 2
/* at this point, x may have had its guts ripped out,
and may be in an unusable state
(apart from being destroyable).
*/
}
The function definitions would look like
void foo (T x) { bar(std::move(x)); }
void bar (T x) { baz(std::move(x)); }
void baz (T x);
Now, from my understanding, foo perfectly forwards its argument x of
type T to bar, which perfectly forwards its argument again to baz. No
deep copies are made, and no rvalue references are present apart from
the move constructor of T.
This is my understanding of how this all works:
At line 2, the argument to foo is the result of std::move, which is an
rvalue. There is still a single definition of foo which takes by
value. In this case, foo's argument will be move constructed, aka only
a shallow move and not a deep copy. Inside the definition of bar, the
same thing happens; we cast x to an rvalue reference with std::move,
causing the argument to baz to be move constructed from the argument
of bar. No deep copies are made. That's perfect forwarding as I
understand it.
At line 1, the argument to foo is x, an lvalue. foo takes its argument
by value. In which case, as it was in C++98, this calls the copy
constructor of T, aka a deep copy, aka no perfect forwarding from main
to foo. However, we will still get perfect forwarding from foo to bar,
and from bar to baz.
Ignoring templates, I don't see the need for rvalue references outside
of move constructors. (Maybe also in the internals of std::move. It's
implemented as a static_cast<T&&>(arg) or something, right?) Move
constructor would take by rvalue reference and swap. operator= would
take by value (which may be a deep copy or just a move) and swap.
Etc.
Now, I'll need to find the proposal on the new template deduction
rules to comment further on templates, but I hope using templates
would be just as simple. This whole reference collapsing scheme seems
entirely unnecessary given that:
- rvalue references only bind to rvalues,
- passing by value will be a move construct when the argument is an
rvalue and the type is move constructable,
- passing by value will be a (deep) copy construct when the argument
is an lvalue or an rvalue and the type is not move constructable, and
the type is move constructable,
- and we have std::move to coerce an lvalue to an rvalue when we no
longer need that object.
Wouldn't you write the templates in just the same way as non-
templates?
Let me put this another way. Let's look at my 3 cases (slightly
revised) and your fourth.
1- Function needs a copy of its argument, in which case it should take
its argument by value.
1.A - It may need just a temporary copy to play with in that function
call only.
1.B - It may need a copy to live past the function call, like
vector::push_back.
1.C - Or it's implemented in terms of another function which needs A
or B. The concern of its implementation propagates to the contract of
the function. If a function you call needs a copy, then you need a
copy, e.g. putting a wrapper over a function doesn't change the
contract, doesn't change the requirement that it needs a copy of its
argument. For example, foo as defined above above does not need a copy
itself, and bar does not need a copy itself, but they're both
implemented in terms of baz which needs a copy. bar calls baz, which
needs a copy, so bar needs a copy, and foo calls bar and thus foo
needs a copy. Thus foo should take its argument by value.
2- Function needs to modify its argument, in which case it should pass
by non-const lvalue reference.
3- Function needs just read access to its argument, in which case it
should pass by const lvalue reference. (For small objects, passing by
value may be more efficient.)
> 4- Needs to perfectly forward its argument to another function, in
> which case it should pass by non-const rvalue reference.
"Needs to perfectly forward its argument to another function" is not
really part of the logical contract of the function. It's an
efficiency concern. (Efficiency is still part of the contract at some
level, but bear with me here.) I would say that case 4 is a subset of
case 1, that you need a copy of the argument. That you want the copy
to be really cheap doesn't change the fact that you want a copy.
(Barring move constructable things only, addressed in a sec.) For
example, vector::push_back. push_back needs a copy of its argument to
outlive the call, and thus it should take by value. If the type is
move constructable and the argument is an rvalue, then this will
result in a shallow move instead of a deep copy, but that doesn't
change the logical contract of the function. The caller can coerce a
move for move constructable types from an lvalue with std::move if the
caller determines that this is appropriate.
Now, for move constructable and not copy constructable types. I would
argue that the same practice should be followed as above. It's just
that if you ever try to make an actual (deep) copy by giving an lvalue
as an argument to a function which takes its argument by value, the
compiler will fail. You as a programmer have two choices. You can
ascertain that the lvalue isn't needed after the call and then can
coerce an rvalue with std::move, or your logic is that you need to
give a copy to the function and still have that variable live after
the function call in the caller's scope, which is impossible for move
constructable and not copy constructable types, so you have a design
problem.
std::move is something very specific to me. It's me telling the
compiler that this object isn't needed anymore. It should be used
(usually) only in the argument to a function, in which case it makes
the compiler choose the move constructor instead of the copy
constructor to construct the actual parameter of the function from the
argument.
Now, I may be missing some vital things in this analysis as I haven't
actually worked with any of this firsthand, but thus far I don't see
the need to ever work with rvalue references outside of move
constructors, and it just seems to complicate things when you do. From
what I've seen, move constructors and std::move plus our current
coding styles would be sufficient to take advantage of this new thing
to the fullest.
This methodology also has the added benefit to spare the programmer
from wasteless copy-paste programming. All of the examples thus far
have had duplicate functions for different argument types, some for
rvalues, some for lvalues, etc. This is just silly to me. You can, and
should, just write as you would for C++98, except for these
guidelines:
- Define move constructors for heavyweight objects.
- Use std::move when you have a live lvalue which you don't need after
a function call which takes it as an argument by value.
- Pass by value according to the above rules, aka pass by value when
you need a copy, a shallow move or a deep copy. (Generally not done as
much in C++98 because we lacked move semantics and std::move to coerce
moving over copying.)
Finally, I'm not sure how well these guidelines work for mixing new
code and old C++98 code. Doing so may result in lots of large copies
unless and until the old types become move constructable. I suppose
you could work around this with a simple wrapper which changes an old
not-move constructable type into a move constructable type, though you
would have an extra layer of indirection so it wouldn't be source-code
seamless. A quick first attempt:
// T is not move constructable
template <typename T>
class moveable_wrapper
{
public:
explicit moveable_wrapper(T * y) : x(y) {} //give up ownership
moveable_wrapper() : x(0) {}
moveable_wrapper(moveable_wrapper const& y) : x(new T(y.x)) {}
moveable_wrapper(moveable_wrapper && y) : x(y.x) { y.x = 0; }
moveable_wrapper& operator= (moveable_wrapper y) { swap(x, y.x);
return *this; }
~moveable_wrapper() { delete x; }
T * x;
};
Optionally, have a wrapper for move-only and disallow copy
construction.
template <typename T>
class move_only_wrapper
{
public:
explicit move_only_wrapper(T * y) : x(y) {} //give up ownership
move_only_wrapper() : x(0) {}
move_only_wrapper(move_only_wrapper && y) : x(y.x) { y.x = 0; }
move_only_wrapper& operator= (move_only_wrapper y) { swap(x, y.x);
return *this; }
~move_only_wrapper() { delete x; }
T * x;
private:
//not implemented, not copyable
move_only_wrapper(move_only_wrapper const& );
};
Note how move_only_wrapper::operator= takes its argument by value.
This will never be a deep copy; the copy constructor is not defined
and not accessible so the compiler will enforce that. It can only be a
shallow move.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: restor <akrzemi1@gmail.com>
Date: Fri, 12 Jun 2009 11:04:26 CST Raw View
> If so, then perhaps what I'm having trouble remembering is that a function
> template declaring an rref parameter doesn't necessarily generate functions
> taking rrefs. Which is actually quite sick, even if it does happen to do the
> right thing for perfect forwarding.
Hi,
I think I may contribute here from the perspective of a C++ user (as
opposed to expert/designer/teacher). After having read all the artcles
I was able to find on rrefs as well as this thread, I do not think
that rrefs are "tricky" but they are simply not introduced in a siple
(at least to me) manner. I think I would find them simple if I read
the intro similar to the following:
This declaration
void fun( Animal & animal );
obviously to everyone (expert and novice alike) indicates that we will
modify an _object_ of type Animal using alias "animal".
This declaration
template< typename T >
void fun( T & obj );
*does not* mean that we will modify any object that we happen to
deduce by alias "obj". This is because T may not be an _object_.
"T" might be a reference, or may be void, or may be a literal. (and
this is a problem in C++98 too).
If you really want to say "take object T by l-ref" you have to say
"object" explicitly:
template< ObjectType T >
void fun( T & obj );
You only want to catch temporary objects (to apply move)? It is
simple:
template< ObjectType T >
void fun( T && obj );
That's it. You do not want to type
template< typename T >
void fun( T & obj );
for at least two reasons:
First, it is an unconstrained template, and all the story about error
messages and early error detection begins...
Second, your template catches references, void and you are risking
producing references to void or references to references, which you
know will be trouble.
I read a number of texts saying whether it is better to type
template< class T >
or
template< typename T >.
Now, you have a third option:
template< ObjectType T >
and you should always prefer it, unless you are either an expert or a
masochist. Next article we will se on C++0x could say somewhere in the
beginning:
C++0x comes with a set of useful additions for beginners:
1. Templates are checked ass soon as they are defined and error
messages are human-parse-able
2. You get the "ObjectType T" idiom which eliminates lots of subtle
mistakes.
Also, if you are an expert you get the tool for perfect forwarding,
but as with every expert tool be careful for reference collapsing. BTW
reference collapsing rules are simple: if there is at least one lref -
you get an lref; only two rrefs render an rref.
Now, I realize, that I said nothing that hasn't already been said in
this thread, but by rearranging the arguments it somehow feels
different.
I hope this is convincing, or I may still need to learn something
important about C++.
Regards,
&rzej
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: ilya sokolov <ilyasokol@gmail.com>
Date: Fri, 12 Jun 2009 11:05:48 CST Raw View
On 11 , 21:47, Scott Meyers <use...@aristeia.com> wrote:
> joshuamaur...@gmail.com wrote:
>
> Specifically, why would you ever define two functions as such:
>
> void f(const Widget&); // copy
> void f(Widget&&); // move from rvalue
>
> Are you suggesting that they have the same behavior? That's where I
> find fault. Either the logic of f
> 1- needs a copy to outlive the call, in which case it should just pass
> by value.
> 2- needs to modify the argument, in which case it should pass by non-
> const lvalue reference.
> 3- needs just read access to the argument, in which case it should
> pass by const lvalue reference.
>
> This is an excellent analysis, thank you very much. I guess we need
> to add a fourth case, however:
>
> 4- Needs to perfectly forward its argument to another function, in
> which case it should
be a template and *declare* its argument as non-const rvalue
reference.
5- Needs to 'capture' the argument (or its copy), is which case is
should *pass* by non-const rvalue reference.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: SG <s.gesemann@gmail.com>
Date: Sun, 14 Jun 2009 03:29:58 CST Raw View
Space Cowboy wrote:
> Scott Meyers wrote:
> > joshuamaur...@gmail.com wrote:
> >
> >> Specifically, why would you ever define two functions as such:
> >>
> >> void f(const Widget&); // copy
> >> void f(Widget&&); // move from rvalue
> >>
> >> Are you suggesting that they have the same behavior?
Actually, yes -- at least logically. Otherwise they should not be
overloaded. We simply don't care about what exactly happens to rvalues
since, well, they're rvalues and any mutation won't be visible because
an rvalue is (usually) a short-lived temporary. I say "usually"
because now we have a syntax for explicitly converting lvalue
expressions to rvalue expressions.
> >> That's where I find fault. Either the logic of f
> >> 1- needs a copy to outlive the call, in which case it should
> >> just pass by value.
> >> 2- needs to modify the argument, in which case it should pass by
> >> non-const lvalue reference.
> >> 3- needs just read access to the argument, in which case it
> >> shouldpass by const lvalue reference.
>
> > This is an excellent analysis, thank you very much. I guess we need
> > to add a fourth case, however:
>
> > 4- Needs to perfectly forward its argument to another function, in
> > which case it should pass by non-const rvalue reference.
>
> > Your fundamental point that there should be no need for the kind of
> > overloading I posted about seems sound to me.
What about vector<T>::push_back?
template<MoveConstructible T>
class vector {
...
void push_back(T const&); // copies argument to vector
void push_back(T &&); // moves argument to vector
...
};
Sure, if you know what T is exactly and that it's efficiently movable
then you can simply use pass-by-value. But in generic code you may
deal with types that are /not efficiently/ movable. In case size(T) is
large and/or T isn't move-optimized, taking it by value involves one
extra move/copy operation you could have saved by using overloaded (&&/
const&) push_back functions.
> Of course, we can still reason about the syntax rules, but there
> shouldn't be a need to explain to someone when to use one and when to
> use another. Instead just explain it away as being convoluted at
> best.
It's only "convoluted" until one understands it. :-)
> However, I'm still not quite sure what you mean when you say
>
> > 4- Needs to perfectly forward its argument to another function, in
> > which case it should pass by non-const rvalue reference.
>
> I'm sorry that I don't have ready access to a compiler which supports
> the extensions, and half the websites googled lead to old descriptions
> based on the old semantics where rvalue references can bind to
> lvalues, so please forgive typos / missing headers / gross
> inaccuracies.
Here's a list of references w.r.t. rvalue references I consider to be
very informative:
* N1377: Move Proposal (2002)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1377.htm
* N1385: Perfect Forwarding Problem (2002)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm
* N2027: A Brief Introduction to Rvalue References (2006)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2027.html
* N2812: A Safety Problem with Rvalue References (2008)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2812.html
* N2844: Fixing a Safety Problem with Rvalue References (2009)
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2844.html
* Rvalue References 101 by Dave Abrahams (2009)
https://www.boostpro.com/trac/wiki/BoostCon09/RValue101
> You want to define a function which perfectly forwards its argument to
> another function. [...]
>
> Let's take some functions foo, bar, and baz. I want to define foo to
> call bar, and bar to call baz, passing along the argument each time.
> foo and bar do not save a copy of the argument, and thus we should be
> able to perfectly forward the argument without making deep copies.
>
> void foo (T x);
> void bar (T x);
> void baz (T x);
>
> [...]
>
> The function definitions would look like
> void foo (T x) { bar(std::move(x)); }
> void bar (T x) { baz(std::move(x)); }
> void baz (T x);
>
> Now, from my understanding, foo perfectly forwards its argument x of
> type T to bar, which perfectly forwards its argument again to baz. No
> deep copies are made, and no rvalue references are present apart from
> the move constructor of T.
I would not consider this kind of forwarding "perfect". Perfect
forwarding doesn't involve any extra construction (be it copy
construction or move construction) compared to calling the final
function directly.
Without templates you achieve this kind of "pass-by-value-semantics"-
ish perfect forwarding via overloading:
void inner(my_type x);
void outer(my_type const& x) { inner(x); }
void outer(my_type && x) { inner(std::move(x)); }
What actually is called "perfect forwarding" is much more generic.
Here's an example for the actual "perfect forwarding" technique for
safely creating unique_ptr objects:
template<typename T, typename ... Params>
inline unique_ptr<T> make_unique(Params && ... params)
{
return unique_ptr<T>(new T(std::forward<Params>(params)...));
}
struct foobar {
foobar (int&, int); // may throw
};
void func(unique_ptr<foobar> up1, unique_ptr<foobar> up1);
int main() {
int lvalue = 23;
func(make_unique<foobar>(lvalue,lvalue+42),
make_unique<foobar>(lvalue,lvalue+54));
// ^^^ is exception-safe as opposed to
// func(unique_ptr<foobar>(new foobar(lvalue,lvalue+42)),
// unique_ptr<foobar>(new foobar(lvalue,lvalue+54)));
}
You see that this kind of perfect forwarding can forward anything (non-
const lvalues, rvalues, ...) without requiring any copy/move
construction of the parameter objects. If you don't really know or
care what this function (or constructor) is supposed to do then this
kind of perfect forwarding is a good choice. If you want pass-by-value-
style forwarding you might want to use the &&/const& overloading idiom
to guarantee that no lvalue argument is modified.
> Ignoring templates, I don't see the need for rvalue references outside
> of move constructors. (Maybe also in the internals of std::move. It's
> implemented as a static_cast<T&&>(arg) or something, right?) Move
> constructor would take by rvalue reference and swap. operator= would
> take by value (which may be a deep copy or just a move) and swap.
> Etc.
It really depends on the types. I think in generic libraries you'll
see &&/const& overloading a lot because std::move is really just a
"move request". T blah (move(x)) may fall back on an expensive copy
c'tor. So, with overloading you can safe one extra object construction
in std::vector<T>::push_back for lvalue arguments at least.
> Now, I'll need to find the proposal on the new template deduction
> rules to comment further on templates, but I hope using templates
> would be just as simple. This whole reference collapsing scheme seems
> entirely unnecessary given that:
> - rvalue references only bind to rvalues,
> - passing by value will be a move construct when the argument
> is an rvalue and the type is move constructable,
Define "move constructible". The concept MoveConstructible<T> doesn't
imply that T has a special move constructor. It just implies that you
can construct a T given a non-const rvalue expression of type T. Every
type that has a copy constructor taking a reference-to-const satisfies
this MoveConstructible concept.
I think the make_unique<T> example nullifies your arguments against
reference collapsing and the template parameter deduction rules. The
std::vector<T>::push_back example nullifies your arguments against
overloading on &&/const& outside of constructors.
Cheers!
SG
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Rick Wheeler <rwheeler@oyogeospace.com>
Date: Sun, 14 Jun 2009 03:28:50 CST Raw View
On Jun 9, 11:33 am, Daniel Kr gler <daniel.krueg...@googlemail.com>
wrote:
> On 8 Jun., 21:21, Scott Meyers <use...@aristeia.com> wrote:
> [..]
>
> > Let's suppose that f should not modify t. Then we presumably have:
>
> > template<class T>
> > requires RvalueReference<T>
> > void f(T t);
>
> > template<class T>
> > requires LvalueReference<T>
> > void f(const T t); // const added
>
> > Is that right?
>
> This is not ill-formed, but the const qualifier is ignored, see
> [dcl.ref]/1:
>
> "[..] Cv-qualified references are ill-formed except when the
> cv-qualifiers are introduced through the use of a typedef
> (7.1.3) or of a template type argument (14.4), in which
> case the cv-qualifiers are ignored. [ Example:
>
> typedef int& A;
> const A aref = 3; // ill-formed; non-const reference initialized with
> rvalue
>
> The type of aref is reference to int , not const reference to int .
> end example ][..]"
>
I think there is a misunderstanding here. As I see it, the quoted text
is actually talking about the cv-qual of the reference itself, not
what it refers to:
int a = 0;
int& ra;
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Rick Wheeler <rwheeler@oyogeospace.com>
Date: Sun, 14 Jun 2009 03:34:41 CST Raw View
On Jun 9, 11:33 am, Daniel Kr gler <daniel.krueg...@googlemail.com>
wrote:
> On 8 Jun., 21:21, Scott Meyers <use...@aristeia.com> wrote:
> [..]
>
> > Let's suppose that f should not modify t. Then we presumably have:
>
> > template<class T>
> > requires RvalueReference<T>
> > void f(T t);
>
> > template<class T>
> > requires LvalueReference<T>
> > void f(const T t); // const added
>
> > Is that right?
>
> This is not ill-formed, but the const qualifier is ignored, see
> [dcl.ref]/1:
>
> "[..] Cv-qualified references are ill-formed except when the
> cv-qualifiers are introduced through the use of a typedef
> (7.1.3) or of a template type argument (14.4), in which
> case the cv-qualifiers are ignored. [ Example:
>
> typedef int& A;
> const A aref = 3; // ill-formed; non-const reference initialized with
> rvalue
>
> The type of aref is reference to int , not const reference to int .
> end example ][..]"
>
I think there is a misunderstanding of (8.3.2)[dcl.ref]/1. The quoted
text as I see it applies to the CV-qual of the reference itself, not
what it refers to.:
int i = 0;
/*1*/ const int& rci = a; // OK
/*2*/ int& const cri = a; // const is ignored (reasonable)
/*3*/ int& volatile vri = a; // volatile is ignored (why?)
(Side note: Deciding to ignore case 3 seems inappropriate. It makes no
sense and deserves an error!)
Therefore, Scott's declaration does not appear ill-formed and the
const should in no way be ignored. With this in mind, it seems the
function should require a const lvalue reference argument here. Is
that the case? If not, then what?
It appears that C++ is attempting to embrace a quantum mechanical view
of language, where spin (number of &'s), color (const/non-const), and
particle decay (ref collapsing) for "Type quarks" results in rvalue
references changing into a lvalue references with the right rotation
and physical laws of interaction (e.g. 14.9.2.1). While the analogy is
a little stretched and may seem humorous, it's not. The implied
euphemism "It helps to have a PhD in quantum physics to use C++" will
prove more scathing than endearing.
There are 4200+ members in comp.std.c++ and assuredly all are
interested in the evolution and progression of the language in a
guarded and consistent manner. Not withstanding the immense effort,
some of the proposed language behaviors put forth here demonstrate
oddities and confoundedness that need to be overcome. Given the
perplexity seen here amidst high comprehensive expertise, consider
that there are orders of magnitude more programmers using the language
who will be impacted.
The items I'm concerned about are:
1. The rref idiom (strongly associated with non-modifiable rvalues)
being transformed to a modifiable lvalue ref. If this is unavoidable,
some additional required notation to that effect would be beneficial.
2. A pass-by-value template syntax that hides an actual pass-by-
reference. If no remedy is available, Greg's permutation necessitating
the ampersands (posted June 9) should be preferred and promoted.
3. The acceptance that it's OK for the compiler to ignore any explicit
construct (e.g. const as was assumed above).
Rick
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Dragan Milenkovic <dragan@plusplus.rs>
Date: Sun, 14 Jun 2009 07:19:49 CST Raw View
Rick Wheeler wrote:
>
> On Jun 9, 11:33 am, Daniel Kr gler <daniel.krueg...@googlemail.com>
> wrote:
>>
>> On 8 Jun., 21:21, Scott Meyers <use...@aristeia.com> wrote:
>> [..]
>>
>>> Let's suppose that f should not modify t. Then we presumably have:
>>> template<class T>
>>> requires RvalueReference<T>
>>> void f(T t);
>>> template<class T>
>>> requires LvalueReference<T>
>>> void f(const T t); // const added
>>> Is that right?
>>
>> This is not ill-formed, but the const qualifier is ignored, see
>> [dcl.ref]/1:
>>
>> "[..] Cv-qualified references are ill-formed except when the
>> cv-qualifiers are introduced through the use of a typedef
>> (7.1.3) or of a template type argument (14.4), in which
>> case the cv-qualifiers are ignored. [ Example:
>>
>> typedef int& A;
>> const A aref = 3; // ill-formed; non-const reference initialized with
>> rvalue
>>
>> The type of aref is reference to int , not const reference to int .
>> end example ][..]"
>>
> I think there is a misunderstanding of (8.3.2)[dcl.ref]/1. The quoted
> text as I see it applies to the CV-qual of the reference itself, not
> what it refers to.:
>
> int i = 0;
> /*1*/ const int& rci = a; // OK
> /*2*/ int& const cri = a; // const is ignored (reasonable)
> /*3*/ int& volatile vri = a; // volatile is ignored (why?)
>
> (Side note: Deciding to ignore case 3 seems inappropriate. It makes no
> sense and deserves an error!)
>
> Therefore, Scott's declaration does not appear ill-formed and the
> const should in no way be ignored. With this in mind, it seems the
> function should require a const lvalue reference argument here. Is
> that the case? If not, then what?
I think you made a mistake...
"const T t" with "T = X &" gives "X & const t". :-)
However, I do agree with both you and Scott that reference collapsing
used together with && to represent that T can be either & or && is
a wrong solution. It is a hack, not a solution.
Not only is the syntax confusing - making us think that this should
accept only rvalue-reference, but it doesn't. Worse part is that
&& really _does_ represent a rvalue-reference, but it is eaten up
by special collapsing rules invented just for this case.
IMHO, confusion on all levels. The notion of "My argument can be either
rvalue-ref or lvalue-ref", should be cleaned up.
--
Dragan
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: "Joe Smith" <unknown_kev_cat@hotmail.com>
Date: Sat, 6 Jun 2009 09:57:40 CST Raw View
"Scott Meyers" <usenet@aristeia.com> wrote:
> As a break, I marveled at the beginning of 14.9.2.1/4 ("In general, the
> deduction process attempts to find template argument values that will make
> the
> deduced A identical to A (after the type A is transformed as described
> above).")
The collapsing rules keep confusing people. Perhaps because there does not
seem to be any good mnemonic for it.
if T=int:
(A) T => int
(B) T& => int&
(C) T&& => int&&
if T=int&
(D) T => int&
(E) T& => int&
(F) T&& => int&
if T=int&&:
(G) T => int&&
(H) T& => int&
(I) T&& => int&&
While that does appear to be exactly what is needed there is very little
symmetry.
The only mnemonic I can think of fails for E, and when looked at in a chart
like the above, (H) really stands out as odd.
Well, Scott, there can be no doubt that once complilers with decent C++0x
support are ready there will be a real need for a new edition of Effecive
C++ to cover some of this.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Mon, 8 Jun 2009 01:16:01 CST Raw View
Joe Smith wrote:
> The collapsing rules keep confusing people. Perhaps because there does not
> seem to be any good mnemonic for it.
>
> if T=int:
> (A) T => int
> (B) T& => int&
> (C) T&& => int&&
>
> if T=int&
> (D) T => int&
> (E) T& => int&
> (F) T&& => int&
For me, the problem is the notion that a function template declaring an
rvalue-ref parameter might generate a function taking an lvalue-ref parameter.
So we see T&& in the source code, and sometimes that yields int&& in a generated
function and sometimes it generates int&. This breaks new ground compared to
C++03, and I find it just plain bizarre. The best way I've found to think of it
is that "T&&" is now a funny kind of variable, the type of which will be deduced
during template argument deduction along with T.
Unfortunately, this explanation breaks down, because if the declared type of the
parameter is const T&&, then the generated type is always an rvalue reference,
even though the usual rule is that const is pretty much orthogonal to everything
else about a type.
I currently view rvalue references as a hack, pure and simple. Everything I've
read suggests they were developed very specifically to solve two problems (move
semantics and perfect forwarding), and the rules for them have been carefully
crafted to solve exactly those two problems.
From what I can tell, it's all very fragile. If you declare your move functions
*exactly* the way you are expected to and if you use std::move and std::forward
*exactly* the way you are expected to, things will probably work the way you
want, but if you do anything else (e.g., constify something you know should
never be modified), things will probably break.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Anthony Williams <anthony.ajw@gmail.com>
Date: Tue, 26 May 2009 16:48:19 CST Raw View
Scott Meyers <usenet@aristeia.com> writes:
> doug.gregor@gmail.com wrote:
>> I believe it was originally an oversight. However, this issue was
>> discussed at the most recent C++ committee meeting, as part of the
>> discussion of N2831, a change that prevents rvalue references from
>> binding to lvalues.
> [...]
>> decided to make rvalue-reference handlers ill-formed. The change is
>> part of N2844 (the successor to N2831), which was voted into the
>> working paper and will appear in the post-Summit mailing.
>
> N2844 proposes that std::move be declared as follows,
>
> template <RvalueOf T> RvalueOf<T>::type move(T&& t);
>
> and that's how it appears in both CD1 (N2800) and the current draft (N2857). I
> must be missing something really basic, because move's parameter here is an
> rvalue reference, and if lvalues no longer bind to rvalue references, how can an
> lvalue be turned into an rvalue via move?
This is a template, so template argument type deduction applies unless
you explicitly specify std::move<SomeType>. The type deduction rules say
that if the function's declared argument type is T&& for some template
parameter T then for an *rvalue* of type U, T==U, but for an *lvalue* of
type U, T==U&. The argument is then (U&)&&, which is a reference to a
reference, and has the type of the base reference (U&).
int i=42;
std::move(i); // T is int&
std::move(42); // T is int
Anthony
--
Author of C++ Concurrency in Action | http://www.manning.com/williams
just::thread C++0x thread library | http://www.stdthread.co.uk
Just Software Solutions Ltd | http://www.justsoftwaresolutions.co.uk
15 Carrallack Mews, St Just, Cornwall, TR19 7UL, UK. Company No. 5478976
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
Author: Scott Meyers <usenet@aristeia.com>
Date: Wed, 27 May 2009 10:37:45 CST Raw View
> There's a special type deduction rule for rvalue value references that
> also involves reference collapsing:
>
> template<typename T>
> void foo(T&& x) {}
>
> void bar() {
> foo(23); // T = int, T&& = int&
> int lv = 23;
> foo(lv); // T = int&, T&& = int&
> }
>
> Perfect forwarding relies on this.
Right, I actually knew all this, I just wasn't able to put the pieces together
until I read your reply. And Daniel Kruegler's. And SG's. More than once.
Then reread 14.9.2.1/3 several times. Then went back and did the whole thing
again. Repeatedly.
As a break, I marveled at the beginning of 14.9.2.1/4 ("In general, the
deduction process attempts to find template argument values that will make the
deduced A identical to A (after the type A is transformed as described
above)."), and by then, your explanation that
T = int, T&& = int&
actually made sense.
Which is a very scary thought.
Thanks.
Scott
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std-c++@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]