Consider the following code:
#include <iostream>
template<typename T>
void inc1(T&& x)
{
T y = x;
++y;
}
template<typename T>
void inc2(T& x)
{
T y = x;
++y;
}
int main()
{
int a = 10;
inc1(a); // a is now 11
int b = 10;
inc2(b); // b remains 10
}
After substitution we have
void inc1(int& x)
{
int& y = x; // reference to x
++y; // increments x
}
void inc2(int& x)
{
int y = x; // copy of x
++y; // increments the copie
}
In inc1
, x
is of type int&
because both int&
and T&&
are references but not both are r-values.
Similarly, in inc2
, x
is of type int&
, because again both int&
and T&
are references, but not both are r-values.
My question is about y
: why in inc1
, y
is of type int&
, while in inc2
, y
is of type int
?
I observed this on both gcc 4.8.1 and microsoft v110 and v120_ctp.
The call by reference method of passing arguments to a function copies the address of an argument into the formal parameter. Inside the function, the address is used to access the actual argument used in the call. It means the changes made to the parameter affect the passed argument.
Reference operator (&) In C Language. This referencing operator is also called address operator, which gives the address of a variable, in which location the variable is resided in the memory.
Passing by by reference refers to a method of passing the address of an argument in the calling function to a corresponding parameter in the called function. In C, the corresponding parameter in the called function must be declared as a pointer type.
No, it doesn't.
In both function calls, what you pass to the function is an int &
(in the sense of: "an lvalue of type int
"). So, presented with the declaration of inc1
, the compiler must deduce T
such that T &&
matches the argument you provided, i.e. int &
. The only way to do this is to assume that T
is int &
, because then T &&
is int & &&
, which is equivalent to int &
. So T
becomes int &
and the local y
is declared as such.
On the other hand, in inc2
, the compiler must deduce T
such that T &
matches the argument type you provided, which is still int &
. That's easiest done by assuming T
is simply int
, so that's what you get for the type of the local y
then.
Reply to a few comments (that have meanwhile been deleted): If you have a function with a predefined argument type, such as
inc3(int x) { /*...*/ }
then, when you call this e.g. as inc3(a)
, the compiler will apply whatever implicit conversion necessary to the argument to make it fit. In the case of inc3(a)
this means to transform a
from int &
(in the sense of lvalue) to int
(in the sense of rvalue) – which is called lvalue-to-rvalue conversion and a valid implicit conversion. It basically amounts to transforming the variable a
into the value it represents at that time.
But when you declare a template, such as inc1
and inc2
from the question, and the function argument is defined in terms of a template parameter, then the compiler will not, or not only, try to apply implicit conversions to the argument to make it fit. Instead, it will choose the argument type parameter T
so it matches the argument type you provided. The rules for this are complicated, but in the case of a T &&
type argument declaration, they work as described above. (In the case of a pure T
argument declaration, an lvalue argument will still undergo lvalue-to-rvalue conversion, and T
will be deduced as int
, not int &
.)
This is why, while int &&
is an rvalue reference, T &&
(where T
is a template parameter) is not necessarily an rvalue reference. Instead, it is whatever results from fitting T
to the argument provided. The expression T &&
in this case has therefore been called universal reference (as oppossed to lvalue or rvalue reference) – it is a reference that becomes either an lvalue or rvalue one, as necessary.
S14.8.2.1 [temp.deduct.call] says:
Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below.
So, we're trying to work out a P given A of type int
.
S14.8.2.3 continues:
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 an rvalue reference to a cv-unqualified template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction. [ Example:
template <class T> int f(T&&); // <--- YOUR TEMPLATE IS LIKE THIS
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&) // <--- YOUR CALL IS LIKE THIS
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
// would bind an rvalue reference to an lvalue
—end example ]
Your call is just like f(i)
in the example - which instantiates a function of the form f<int&>(int&)
... i.e. T
is int&
, which is why T y = x
creates a reference to x
.
See also Scott Meyers's page at http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With