Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Const and non-const reference binding

void swap(int& a, int& b) {}

void copy(int& a, const int& b) {}

int main() {
   int a=1;
   unsigned b=2;

   swap(a, b);
   copy(a, b);
}

C++ language, the g++ compiler.

Please tell me why there won't be a compilation error in a copy-function call, but in a swap-function triggers invalid initialization of reference of type ‘int&’ from expression of type ‘unsigned int’.

Sorry for my bad English.

like image 757
kunteynir Avatar asked Oct 04 '22 18:10

kunteynir


1 Answers

Short story

First of all - the rules L for reference binding of an expression of type U with qualification cv2 to a reference of type T with qualification cv1.:

A reference to cv1 T can be initialized by an expression of type cv2 U

  • if the reference is an lvalue reference and the initializer expression

    • is an lvalue and cv1 T is reference-compatible with cv2 U, or
    • has a class type [ ... ].
  • Otherwise, cv1 shall be const or the reference shall be an rvalue reference.

cv1 T is reference-compatible to cv2 U if T is the same types as U (or a base of U) and if cv1 is equal to cv2(or greater).

Unfortunatelly (or luckily?! ;)) a function with a non-constant lvalue reference parameter cannot be called with an lvalue of a non-reference-compatible type (or in case of class types without a viable conversion of the passed argument to a reference-compatible type).

In detail

Let's consider a function that takes an integer reference and a second one that has a constant integer reference paramter.

void doSomething (int & x)
{
  // do some READ & WRITE stuff with x
  x = x+5;
}

int doSomethingElse (int const & x)
{
  // do some READ ONLY stuffwith x
  return 3*x;
}

Let's look at a single signed and another unsigned value:

 int a = 1;
 unsigned int b = 2;

Now we pass the int named a to doSomething():

 // works since x of doSomething can bind to a
 doSomething(a);

 // let's try to expand/"inline" what is happening
 {
   int & x = a;
   x = 5;
 }

No magic here, reference x binds to a and is set to 5 (and therefore a, too). Fine.

Now we try to pass b to the same function. But ...

   // ... it doesn't work/compile since
   // unsigned int is not reference compatible to int
   doSomething(b); // compile error here

   // what's going on here
   {
     int & x = b; // unsigned value cannot bind to a non-const lvalue reference!
                  // compile error here
     x = 5;
   }

Here we start having trouble and calling doSomething with b will not compile.

Let's look at the const reference function now. Passing a is obviously not problematic again. A const int reference is bound to an int value.

int c = doSomethingElse(a);
 
// let's do some naive inlining again
int c;
{
  int const & x = a;
  c = 3*x;
}    

Well seems alright. c will be 3*a.

Now what happens if we pass b to that function? The standard says that, in this case, a temporary of type cv1 T is created and initialized from the initializer expression using copy initialization rules.

int d = doSomethingElse(b);

// expanding this to:
int d;
{
  int temp = b; // implicit conversion, not really an lvalue!
  int const & x = temp;
  d = 3*x;
} 
like image 130
Pixelchemist Avatar answered Oct 08 '22 00:10

Pixelchemist