Int VS Const Int&

int vs const int&

In C++ it's very common what I consider an anti-pattern that uses const T& like a smart way of just saying T when dealing with parameters. However a value and a reference (no matter if const or not) are two completely different things and always and blindly using references instead of values can lead to subtle bugs.

The reason is that when dealing with references you must consider two issues that are not present with values: lifetime and aliasing.

Just as an example one place where this anti-pattern is applied is the standard library itself, where std::vector<T>::push_back accepts as parameter a const T& instead of a value and this can bite back for example in code like:

std::vector<T> v;
...
if (v.size())
v.push_back(v[0]); // Add first element also as last element

This code is a ticking bomb because std::vector::push_back wants a const reference but doing the push_back may require a reallocation and if that happens means that after the reallocation the reference received would not be valid any more (lifetime issue) and you enter the Undefined Behavior realm¹.

Much better from a logical point of view in today C++ would be to accept a value (i.e. void std::vector<T>::push_back(T x)) and then efficiently moving that value in the final place inside the container. Then the caller may eventually use std::move if that is deemed important (note however that the idea of moving construction was not present in original C++).

Aliasing issues are instead a source of subtle problems if const references are used instead of values. I've been bitten for example by code of this kind:

struct P2d
{
double x, y;
P2d(double x, double y) : x(x), y(y) {}
P2d& operator+=(const P2d& p) { x+=p.x; y+=p.y; return *this; }
P2d& operator-=(const P2d& p) { x-=p.x; y-=p.y; return *this; }
};

struct Rect
{
P2d tl, br;
Rect(const P2d& tl, const P2d& br) : tl(tl), bt(br) {}
Rect& operator+=(const P2d& p) { tl+=p; br+=p; return *this; }
Rect& operator-=(const P2d& p) { tl-=p; br-=p; return *this; }
};

The code seems at a first glance pretty safe, P2d is a bidimensional point, Rect is a rectangle and adding/subtracting a point means translating the rectangle.

If however to translate the rectangle back in the origin you write myrect -= myrect.tl; the code will not work because the translation operator has been defined accepting a reference that (in that case) is referencing a member of same instance.

This means that after updating the topleft with tl -= p; the topleft will be (0, 0) as it should but also p will become at the same time (0, 0) because p is just a reference to the top-left member and so the update of bottom-right corner will not work because it will translate it by (0, 0) hence doing basically nothing.

Please don't be fooled into thinking that a const reference is like a value because of the word const. That word exists only to give you compile errors if you try to change the referenced object using that reference, but doesn't mean that the referenced object is constant. More specifically the object referenced by a const ref can change (e.g. because of aliasing) and can even get out of existence while you are using it (lifetime issue).

In const T& the word const expresses a property of the reference, not of the referenced object: it's the property that makes impossible to use it to change the object. Probably readonly would have been a better name as const has IMO the psychological effect of pushing the idea that the object is going to be constant while you use the reference.

You can of course get impressive speedups by using references instead of copying the values, especially for big classes. But you should always think about aliasing and lifetime issues when using references because under the cover they're just pointers to other data.
For "native" data types (ints, doubles, pointers) references however are actually going to be slower than values and there's nothing to gain in using them instead of values.

Also a const reference will always mean problems for the optimizer as the compiler is forced to be paranoid and every time any unknown code is executed it must assume that all referenced objects may have now a different value (const for a reference means absolutely NOTHING for the optimizer; that word is there only to help programmers - I'm personally not so sure it's such a big help, but that's another story).


(1) Apparently (https://stackoverflow.com/a/18794634/320726) the standard says that this case is valid but even with this interpretation (on which I do not agree at all) still the problem is present in general. push_back doesn't care about the identity of the object and so should have taken the argument by value. When you pass a const reference as value to a function it's your responsibility to ensure that the referenced object will stay alive for the full duration of the function. With v.push_back(v[0]) this is simply false if no reservation was done and IMO (given the push_back signature) is a caller's fault if that happens. The real logic bug is however the push_back interface design (done intentionally, sacrificing logical correctness on the altar of efficiency). Not sure if it was because of that defect report but I saw a few compilers "fixing" the problem in this special case (i.e. push_back does a check to see if the element being pushed is coming from the vector itself).

int vs const int&, And when I use it

You should generally use references if you plan on continuing to use the value you are passing into a function after that function completes. As you have highlighted, sometimes it is more costly to pass a reference. This can happen if your data is very small (you normally wouldn't bother passing integers as arguments), or if you have an opportunity to move the contents of the variable (though this can be a more complicated topic).

Using references allows you to avoid copying data while still guaranteeing that the argument is valid, since unlike with pointers there is no null reference. References give you the opportunity of allowing the function to mutate the contents of your variable, or with const references you can prevent a function from modifying the variable you pass in. In general you should prefer to use references over pointers when you don't need features only available with a pointer, and prefer const references over references when you don't need mutation.

Whats the difference between const int const& and const int& in C++?

So is const int const& a reference to a const int?

No, that's a compilation error.

Try compiling something like :

int z = 0;
const int const& a= z;

And you'll see the compiler will yell at you.

You can't duplicate const for the same type. Your premise that const int const* is a constant pointer to a constant int is wrong too.

What is the difference between const int& jj and int& const jj ?

const int& means reference to const int. (Similarly, int& means reference to non-const int.)

int& const literally means const reference (to non-const int), which is invalid in C++, because reference itself can't be const-qualified.

$8.3.2/1 References [dcl.ref]

Cv-qualified references are ill-formed except when the cv-qualifiers
are introduced through the use of a typedef-name ([dcl.typedef],
[temp.param]) or decltype-specifier ([dcl.type.simple]), in which case
the cv-qualifiers are ignored.

As you said, references are inherently constant and once set they cannot be changed to refer to something else. (We can't rebind a reference after its initialization.) This implies reference is always "const", then const-qualified reference or const-unqualified reference might not make sense actually.

Assigning double to const int& vs int to const int&

Even though it looks like, ref isn't a reference to i.

double i = 10;
const int &ref = i; // ***
i = 20;

A reference to an int cannot refer to a double. Therefore, in the marked line, a conversion from a double to an int takes place and the result of conversion is a temporary rvalue. Normaly, they are destroyed when the full expression they were created in ends. But there's a rule in C++ that allows extending the lifetime of a temporary when it is bound to a reference to const. It is extended until the reference dies. For that, the compiler actually allocates some memory for it behind the scenes.

The assignment modifies the original object i, the unnamed temporary stays intact.

What does const int& do as a return type?

In general, returning a reference avoids that the return value gets copied, and (if it is not const-qualified) gives you the opportunity to change the "original" value. A return type const T& is often used in conjunction with objects of class type, e.g. std::vector, where copying could result in (unwanted) overhead. In conjunction with elementary data types like int, however, passing back a const int& is likely to be more overhead than copying the raw int value.

Further, in your example, if you assign the result of type const int& to a non-reference type, e.g. int largest = max(...), the return value is "dereferenced" and assigned by value. So you can change the variable's content as it is a copy of the const-referenced value. If you have a type const int&, and you assign it to a variable of type const int&, then you get a reference, yet the compiler will not allow you the change it's contents.

So the only thing where passing back a reference to int would be without const, which will then allow you to alter the contents of the "origninal" value.
See the following code; Hope it helps a bit:

int& max(int a[], int length) {
return a[3];
}

const int& maxConst(int a[], int length) {
return a[3];
}

int main(){

int array[] = {12, -54, 0, 123, 63};

int largest = max(array,5);
cout<< "Largest element (value) is " << largest << endl;
largest = 10;
cout << "Element at pos 3 is: " << array[3] << endl;

int largestConst = maxConst(array,5);
cout<< "Largest element (value) is " << largestConst << endl;
largestConst = 10;
cout << "Element at pos 3 is: " << array[3] << endl;

int &largestRef = max(array,5);
cout<< "Largest element (ref) is " << largestRef << endl;
largestRef = 10;
cout << "Element at pos 3 is: " << array[3] << endl;

const int &largestRefConst = maxConst(array,5);
cout<< "Largest element (value) is " << largestRefConst << endl;
// largestRefConst = 10; // -> cannot assign to variable with const-qualified type
//cout << "Element at pos 3 is: " << array[3] << endl;

return 0;
}

Output:

Largest element (value) is 123
Element at pos 3 is: 123
Largest element (value) is 123
Element at pos 3 is: 123
Largest element (ref) is 123
Element at pos 3 is: 10
Largest element (value) is 10

Difference between return values int& and const int&

Here is a simple program demonstrating the difference between the two:

#include <iostream>

using namespace std;

class Foo {
int c;
public:
Foo() {
c = 1;
}
int abc() {
c++;
cout << "non-const, c = " << c << endl;
return c;
}

const int& abc() const {
//c++; // compile-error, can't modify in a const function
cout << "const, c = " << c << endl;
return c;
}
};

int main() {
const Foo foo1;
Foo foo2;

int a = foo1.abc();
int b = foo2.abc();

cout << "a = " << a << endl;
cout << "b = " << b << endl;

a++; b++;

cout << "a = " << a << endl;
cout << "b = " << b << endl;

cout << foo1.abc() << endl;
cout << foo2.abc() << endl;
}

The output is

const, c = 1
non-const, c = 2
a = 1
b = 2
a = 2
b = 3
const, c = 1
1
non-const, c = 3
3

The first function is allowed to modify the member variable c, whereas the second cannot. The appropriate function is called depending on const qualification.

It is common to have a function paired with a const qualified version of it. See, for example, operator[] for std::vector.

What is the difference between const int*, const int * const, and int const *?

Read it backwards (as driven by Clockwise/Spiral Rule):

  • int* - pointer to int
  • int const * - pointer to const int
  • int * const - const pointer to int
  • int const * const - const pointer to const int

Now the first const can be on either side of the type so:

  • const int * == int const *
  • const int * const == int const * const

If you want to go really crazy you can do things like this:

  • int ** - pointer to pointer to int
  • int ** const - a const pointer to a pointer to an int
  • int * const * - a pointer to a const pointer to an int
  • int const ** - a pointer to a pointer to a const int
  • int * const * const - a const pointer to a const pointer to an int
  • ...

And to make sure we are clear on the meaning of const:

int a = 5, b = 10, c = 15;

const int* foo; // pointer to constant int.
foo = &a; // assignment to where foo points to.

/* dummy statement*/
*foo = 6; // the value of a can´t get changed through the pointer.

foo = &b; // the pointer foo can be changed.



int *const bar = &c; // constant pointer to int
// note, you actually need to set the pointer
// here because you can't change it later ;)

*bar = 16; // the value of c can be changed through the pointer.

/* dummy statement*/
bar = &a; // not possible because bar is a constant pointer.

foo is a variable pointer to a constant integer. This lets you change what you point to but not the value that you point to. Most often this is seen with C-style strings where you have a pointer to a const char. You may change which string you point to but you can't change the content of these strings. This is important when the string itself is in the data segment of a program and shouldn't be changed.

bar is a constant or fixed pointer to a value that can be changed. This is like a reference without the extra syntactic sugar. Because of this fact, usually you would use a reference where you would use a T* const pointer unless you need to allow NULL pointers.



Related Topics



Leave a reply



Submit