template<typename T>
void foo(T&& arg);
I know if arg is an lvalue, e.g. int x = 0; foo(x);
then T = int&
and the function will be foo(int& &&)
, which is foo(int&)
.
if arg is an rvalue, e.g. foo(0)
; then T = int
and the function will be foo(int&&)
.
What if I have
template<typename T>
void foo(T& arg);
template<typename U>
void bar(U&& u)
{
foo(u);
}
What would be T
in foo
when calling bar(0)
?
template<typename U>
void bar(U&& u)
{
foo(u);
}
Regardless of what you pass in to bar
, u
is an lvalue because it has a name.
u
may be an lvalue reference or an rvalue reference, but when you pass it to foo
, its "reference-ness" is ignored, as per usual.
Forget about lvalues and rvalues and templates for a moment. A reference is supposed to be an alias for another variable, and refering to a reference by name is supposed to behave, in most cases, just as if you were refering to the original variable:
int i = 42;
int& r = i;
f(int);
f(int&);
f(i); // error: call to ambiguous overload
f(r); // error: call to ambiguous overload
g(int);
g(r); // OK, pass by copy, reference-ness of r is irrelevant
h(int&);
h(i); // OK, pass by reference, non-reference-ness of i is irrelevant
In the function call statements above, the id-expressions i
and r
are lvalues of type int
. The fact that the variables i
and r
are a non-reference and a reference respectively has no bearing on the type or value category of the corresponding id-expressions. This is the way references have always worked, and rvalue references don't change that.
template<typename T>
void foo(T& arg);
There is nothing you could pass to foo
that would make T
be a reference type. arg
will always be an lvalue reference.
If you want to propagate the value category of an argument you need std::forward
:
template<typename U>
void baz(U&& u)
{
foo(std::forward<U>(u));
}
baz(42); // error: attempted to call foo() with an rvalue