Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reference interpretation in c++

Tags:

c++

c++11

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.

like image 321
a.lasram Avatar asked Jun 04 '13 05:06

a.lasram


People also ask

What does reference mean in C?

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.

What is referencing operator in C?

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.

What is pass by reference in C?

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.

Is there reference in C?

No, it doesn't.


2 Answers

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.

like image 103
jogojapan Avatar answered Sep 19 '22 05:09

jogojapan


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

like image 43
Tony Delroy Avatar answered Sep 19 '22 05:09

Tony Delroy