Template argument as rvalue reference

J

Juha Nieminen

There's a special rule in the new standard with regard to rvalue references
when used with a template argument. Namely, if you have T&& (where T is
a template argument), it will be collapsed to T& if an lvalue is given
to it.

This means that if you have something like this:

template<typename T> void foo(T&&, T&&);

then two different functions will be instantiated depending on whether
you call it with rvalues or lvalues. In other words foo(1, 2) will
generate one function and foo(a, b) (where a and b are existing variables)
a different one.

What I do not understand is why calling foo(a, 1) or foo(1, b) doesn't
compile. It gives an error (at least with clang).
 
S

SG

Am 26.10.2012 12:23, schrieb Juha Nieminen:> There's a special rule in
the new standard with regard to rvalue references
when used with a template argument. Namely, if you have T&& (where T
is a template argument), it will be collapsed to T& if an lvalue is
given
to it.

No, reference collapsing is always applicable. It is not restricted to
the case of templates. The only restriction w.r.t. reference collapsing
is that you cannot actually type things like

typedef int && & lvalue_reference;

But you can write

typedef int&& rvalue_reference;
typedef rvalue_reference& lvalue_reference;

The special rule w.r.t. rvalue references is a deduction rule. That is,
if a function receives a parameter of type T&& where T will be deduced,
a special rule kicks in which might render T to be an lvalue reference.
This is done depending on the argument you use to invoke the function
template.
This means that if you have something like this:

template<typename T> void foo(T&&, T&&);

This is not the way to enable perfect forwarding.
then two different functions will be instantiated depending on whether
you call it with rvalues or lvalues. In other words foo(1, 2) will
generate one function and foo(a, b) (where a and b are existing
variables) a different one.

What I do not understand is why calling foo(a, 1) or foo(1, b) doesn't
compile. It gives an error (at least with clang).

Because it's a template argument deduction error.

template<typename T> void foo(T&&, T&&);

int main() {
int a=0, b=0;
foo(1,2); // --> T=int --> T&&=int&&
foo(a,b); // --> T=int& --> T&&=int&
foo(1,b); // deduction error
}

It's a deduction error, because the deduction of T for the first
parameter yields T=int while the deduction of T for the second parameter
yields T=int&&. This is inconsistent and qualifies as deduction failure.
Since no other function or function template called foo is available,
the compiler complains about not being able to call the right foo.

Ok, so, you might think that if the compiler isn't able to figure out
what T is ... how about specifying T? Let's try ...

foo<int>(1,b); // error #1
foo<int&>(1,b); // error #2

#1 is an error because T=int makes the second parameter an rvalue
reference but an rvalue reference of type int is not allowed to be
initialized with an lvalue of int -- for safety reasons.

#2 is an error because T=int& makes the first parameter an lvalue
reference but an lvalue reference of type int is not allowed to be
initialized with an rvalue of int -- for safety reasons, too.

HTH,
SG
 
S

SG

Am 26.10.2012 12:38, schrieb SG:
template<typename T> void foo(T&&, T&&);

int main() {
int a=0, b=0;
foo(1,2); // --> T=int --> T&&=int&&
foo(a,b); // --> T=int& --> T&&=int&
foo(1,b); // deduction error
}

It's a deduction error, because the deduction of T for the first
parameter yields T=int while the deduction of T for the second parameter
yields T=int&&. This is inconsistent and qualifies as deduction failure.
^^^^^^^
Sorry, this was supposed to be "T=int&".
 
J

Juha Nieminen

SG said:
No, reference collapsing is always applicable. It is not restricted to
the case of templates.

I think it is. If you have a non-templated rvalue reference parameter,
you can't give it an lvalue. You'll get a compile error. (I think that
it used to be for a long time that an lvalue could be given to something
taking an rvalue reference, but the changed it much later in the
standardization process.)

The only situation where an rvalue reference is automatically collapsed
to a regular reference is with templates.
This is not the way to enable perfect forwarding.

How is that relevant to my question?
 
S

SG

Am 26.10.2012 13:20, schrieb Juha Nieminen:
I think it is.

Then you are wrong. Or perhaps you have a different idea about what
"reference collapsing" means.
If you have a non-templated rvalue reference parameter,
you can't give it an lvalue. You'll get a compile error.

What is an "rvalue reference parameter"?

typedef int& foo;

void bar(foo&& x);

void test() {
int a = 23;
bar(a); // actually works
}

Is x of bar an rvalue reference parameter? Well, it looks like one. But
it is not. Due to reference collapsing x is actually an lvalue
reference. This is reference collapsing outside of the context of
templates. Please don't confuse reference collapsing with template
argument deduction.
The only situation where an rvalue reference is automatically collapsed
to a regular reference is with templates.

I think you're mixing two different things: reference collapsing and
template argument deduction. The former has nothing to do with templates.
How is that relevant to my question?

I dunno. Maybe you're trying to do/understand perfect forwarding. At
least it's the main reason why anybody would use T&& as function
parameter type where T is deduced. In that case, the information that
T&& should only be used for one parameter is useful.

Cheers!
SG
 
S

SG

Am 26.10.2012 15:19, schrieb SG:
Am 26.10.2012 13:20, schrieb Juha Nieminen:

Then you are wrong. Or perhaps you have a different idea about what
"reference collapsing" means.


What is an "rvalue reference parameter"?

typedef int& foo;

void bar(foo&& x);

void test() {
int a = 23;
bar(a); // actually works
}

Is x of bar an rvalue reference parameter? Well, it looks like one. But
it is not. Due to reference collapsing x is actually an lvalue
reference.

Let me stress that this reference collapsing has nothing to do with the
function call or the lvalue 'a' for that matter. Reference collapsing
applies because I wrote "foo&&" where foo is already a reference. The
reference collapsing rules turn foo&& into an lvalue reference as well,
because "lvalue references win", or, to put it differently: & + && = &.
 
P

ptyxs

Le 26/10/2012 15:29, SG a écrit :
Am 26.10.2012 15:19, schrieb SG:

Let me stress that this reference collapsing has nothing to do with the
function call or the lvalue 'a' for that matter. Reference collapsing
applies because I wrote "foo&&" where foo is already a reference. The
reference collapsing rules turn foo&& into an lvalue reference as well,
because "lvalue references win", or, to put it differently: & + && = &.
 
J

Juha Nieminen

SG said:
What is an "rvalue reference parameter"?

Something like this:

void foo(int&& i);

If you try to give an lvalue to that, it gives an error.

I must admit, I do not know the new standard enough to understand what
exactly is going on here, and why it makes a difference (and why you need
a typedef):
 
S

SG

Am 27.10.2012 21:12, schrieb Juha Nieminen:
Something like this:

void foo(int&& i);

If you try to give an lvalue to that, it gives an error.

Right. The thing is, if you write

template<class T> void blah(T&& x) {...}

x is not necessarily an rvalue reference parameter. It looks like one.
And it may be one depending on what T is. But is also may be an lvalue
reference. The reference collapsing rule "&+&&=&" makes x an lvalue
reference in case T is an lvalue reference type, otherwise x is an
rvalue reference. That's reference collapsing.

In addition to reference collapsing we have a special template argument
deduction rule which automatically picks the "right T" to make
initializing a parameter of type T&& possible. That is, if the argument
was an lvalue, the corresponding template type parameter is deduced to
be an lvalue reference type, otherwise it is a non-reference type. Why?
Because these rules are the rules they came up with to enable "perfect
forwarding".
I must admit, I do not know the new standard enough to understand what
exactly is going on here, and why it makes a difference (and why you need
a typedef):

The syntax does not allow "int& && x". I guess that is because there
would be no point in allowing it since you could just as well write
"int& x".

If you havn't already seen one of Scott Meyers' latest talks about
rvalue references, I encourage you to take a look.

HTH,
SG
 
Z

Zhihao Yuan

template<typename T> void foo(T&&, T&&);

Let's stop repeating the materials all over the internet. The
following is exactly what you want:

template<typename T1, typename T2> void foo(T1&&, T2&&,
typename std::enable_if<std::is_same<
typename std::remove_reference<T1>::type,
typename std::remove_reference<T2>::type>::value>::type* = 0)
{}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,480
Members
44,900
Latest member
Nell636132

Latest Threads

Top