Const and Non-Const Operator Overloading - c++

I have a topic I'm confused on that I need some elaborating on. It's operator overloading with a const version and a non-const version.
// non-const
double &operator[](int idx) {
if (idx < length && idx >= 0) {
return data[idx];
}
throw BoundsError();
}
I understand that this lambda function, takes an index and checks its validity and then returns the index of the array data in the class. There's also a function with the same body but with the function call as
const double &operator[](int idx) const
Why do we need two versions?
For example, on the sample code below, which version is used in each instance below?
Array a(3);
a[0] = 2.0;
a[1] = 3.3;
a[2] = a[0] + a[1];
My hypothesis that the const version is only called on a[2] because we don't want to risk modifying a[0] or a[1].
Thanks for any help.

When both versions are available, the logic is pretty straightforward: const version is called for const objects, non-const version is called for non-const objects. That's all.
In your code sample a is a non-const object, meaning that the non-const version is called in all cases. The const version is never called in your sample.
The point of having two versions is to implement "read/write" access for non-const objects and only "read" access for const objects. For const objects const version of operator [] is called, which returns a const double & reference. You can read data through that const reference, but your can't write through it.

To supply a code example to complement the answer above:
Array a(3);
a[0] = 2.0; //non-const version called on non-const 'a' object
const Array b(3);
double var = b[1]; //const version called on const 'b' object
const Array c(3);
c[0] = 2.0; //compile error, cannot modify const object

I think if there was an option like
double k = 3.0;
and the array's elements were const
a[0] = a[1] + k; or std::cout << a[0] + k;
the "const double &operator[](int idx) const" version would have been called, here you are adding non const variable to const object;

Related

The difference between returning by reference and a pointer in a const function

I'm investigating some C++ code, and I'd like to know why this code doesn't compile:
class A {
int K;
const int* f(const int* k) const {
return *k;
}
};
and this code does compile:
class A {
int K;
const int* f(const int* k) const {
return &K;
}
};
I dont understand what the difference is because I'm trying to return a const in the first one as well.
Is &K const because it is an address?
A pointer can hold the memory address of an object. Now, since your function const int* f(const int* k) const returns a const pointer to an int, that means you have to return to it either a const pointer to an int or the memory address of an int.
In your first code, you dereference your pointer and return it to the function, which means you're returning a value. This doesn't work by the above argument.
Your 2nd code, however, compiles since you return to f an address of an object (in your case the memory address of the integer int K).
In the first case you're dereferencing const int* k pointer which means you're trying to return an int value whereas function const int* f(const int* k) const expects to return pointer const int*.
In the second case you're using & which returns an address of your int K class member and because const int* f(const int* k) const function expect that the return type will be pointer to const int* it does compile without a problem.
You are probably confused by the syntax here. Despite appearances, operator * returns a reference, while operator & returns a pointer. For example:
int k0 = 0; // declare a regular int variable
int* k1 = &k0; // declare a pointer to int and assign the address of k0 to it
int& k2 = *k1; // declare a reference to int and make it refer to the value pointed by k1
So as you can see the meanings of & and * is sort of switched when used in an expression rather that in type declaration.

Why is it erroneous to return address of a std::vector element via a const function?

I have looked at the following threads but they don't talk about the constness of address returning functions:
(1) Returning a pointer to a vector element in c++
(2) It's safe to return address of a std::map value?
In the code:
class test_c{
private:
std::vector <double> dblvec;
double *dblarray;
public:
double *AddVecElem(int index){return &dblvec[index];} //Fine
double *AddVecElem(int index) const {return &dblvec[index];} //compiler error "Return value type does not match function type"
double *AddArrElem(int index){return &dblarray[index];}//Fine
double *AddArrElem(int index) const {return &dblarray[index];}//Fine
};
Only the case where returning the address of a vector element's address as a const function produces a compiler error.
Why is this? How can returning the address of a vector element affect the state of the object?
Because dblvec[index] in a const function would call:
const_reference operator[]( size_type pos ) const;
And as of that &dblarray[index] is of the type const double *, and this cannot be converted to double *.
dblarray[index] on the other hand is a double so you return a double *
Check the question What can a 'const' method change? for more details about what is const if you are within a const function.
If you declared function as class const function, what means that you can't change the class's variables from the function, you can't return an address of class's variable, unless you'll promise that this function returns a non-changeable (const) value.
Inside the const function, the function looks at the class's variables as const variables, so when you try to return the address, you actually return const double * and not double * as the function requires.
The solution for this case is to return a pointer to const T:
const double* AddVecElem(int index) const {return &dblvec[index];}
Or, as you mentioned to declare this function as non-const function.
Another solution is to return a non-pointer value (copy of the value), and use void setValue(..) function to change this class's variable data.
As for double *dblarray your function doesn't returns the pointer itself, it returns a copy of your variable (pay attention- your function returns double* and the variable is within the same pointer level- so it returns a copy). It is just like the following example:
private:
int value;
public:
int return_value() const {
return value; // Copy of the const returns - you don't change class's variable data
}
int* return_pointer_value() const {
return &value; // Compiler error - expected int* returns const int*
}
So, if you want to make the pointer situation equals to the vector's one, you should return double** and not double*:
double **AddArrElem(int index) const {
return &dblarray; // Compiler error - expected double** returns const double *const*
// (*const)=> for 'dblarray', (*)=> for '&'
}
Then why the vector behave differently? Why you can't returns &dblvec[index]?
As mentioned in #t.niese post, the call to the operator function of vector recognize that it's a const vector, so it automatically returns const double- let's have a look of this function:
double& AddVecElem(int index) const {
return dblvec[index]; // Compiler error - Expected double returns const double
}
We want to return reference to this variable, but the returned variable is a constant. So when you want to return double* you actually returns const double*.
Even when you try to go around and call to vector.data() it returns you const double * instead of double* - And this is the difference between the vector and the pointer.
As reference: Can const member function return a non-const pointer to a data member?

Is it impossible to return a reference to a const pointer to const data?

I am having some problems trying to return a reference to a const pointer to const data. In the following code, get_pC returns a reference to a const pointer to data:
#include <iostream>
using namespace std;
class C {
public:
double c;
};
class A {
public:
C* pC;
A(const double val):pC(new C){pC->c=val;};
};
class B {
public:
const A* pA;
B(const A& a):pA(&a){};
C * const & get_pC() const { return pA->pC; }
};
int main() {
A a(3.7);
B b(a);
C * const & r = b.get_pC();
r->c=2.2;
cout<<(long) &b.pA->pC<<endl;
cout<<(long) &r<<endl;
}
By compiling this code, there is no error shown so it is allowed to modify "c" through "r". That's ok. Moreover, the address of b.pA->pC matches the address of r. That is nice too ^_^
But, when I try to disallow the modification of "c" through "r" is when I experience problems. If I add const to the declaration of "r":
const C * const & r = b.get_pC();
then the compiler complains about the modification of "c". Perfect, that's exactly what I want... right? Unfortunately no, now the addresses of "r" and b.pA->pC are different!!!
Is it impossible to do what I am trying to do? I know that it would be possible to do something "similar" by returning a pointer instead of a reference:
const C * const * get_pC() const { return &pA->pC; }
const C * const * r = b.get_pC();
but it would add one level of indirection and I am curious to know if it is really impossible or there is a way to make it work with references.
A simpler version of the "problem" would be:
int x;
int *p = &x;
int* const& r1 = p;
const int* const& r2 = p;
In this case r1 binds directly to p, so &r1 == &p. However, r2 cannot bind directly to p. Instead, the r2 line creates a temporary const int * object and binds r2 to the temporary.
This is always a possibility when using const lvalue references; if the type is not right for direct binding, but an implicit conversion exists, then a temporary may be created.
To avoid the temporary you would need to use a cast:
const int* const & r2 = const_cast<const int* &>(p);
or in your original code:
const C * const & r = const_cast<const C * &>(b.get_pC());
Remark: It's considered poor style to store the address of something that was passed by reference (because the caller does not expect this to happen and the object may end its lifetime). Consider redesigning your code rather than actually using this const_cast solution; e.g. use C * const * get_pC() const { return &pA->pC; } and const C * const * r = b.get_pC();
To summarize the relevant reference binding rule: references only bind directly if the two types are the same, (or a base class reference can bind to derived class), with the left-hand side type possibly having extra top-level qualifiers. See here for more detail, or [dcl.init.ref] section of the Standard.
Unfortunately no, now the addresses of r and b.pA->pC are different!!!
My guess is that in the process of converting C * const & to C const* const &, the compiler creates a temporary of type C const* and r is a reference to that. It's as if you are using:
C const* temp = b.get_pC();
C const* const& r = temp;

Why is dereferenced element in const vector of int pointers mutable?

I am not sure the true meaning of const vector<int *> so I compiled the code below to get an idea but am now more confused.
vector<int *> v;
int x = 1, y = 2;
v.push_back(&x);
v.push_back(&y);
const vector<int *> w = v;
w[0] = &y; //failed. Element is a constant pointer?
*(w[0]) ++; //failed. Element pointer references to constant value?
If I had stopped here, I would have assumed that const vector<int *> is a vector of const int * const, but then I tried the following which clearly contradicted that assumption.
*(w[0]) += 3; //passed. Value not constant?
*(w[0]) = 20; //passed. Why...
Now *(w[0]) for reason unknown to me obviously treats ++ and += and assignment differently. I convinced myself that const vector only declares a constant object of the vector class and that the above results might depend on the actual implementation of the operator overloading of vector class. But I can't wrap my head around this. Can anyone help explain, please?
If it is relevant, I used g++ 4.2 on a Mac.
Why is dereferenced element in const vector of int pointers mutable?
For const vector<int *>, the element would be const pointer to non-const, i.e. int * const, so you can modify the object pointed by the pointer, but not the pointer itself.
According to Operator Precedence, postfix increment operator has higher precedence than operator*, so *(w[0]) ++; is equivalent to
* ((w[0]) ++);
The increment on the pointer is performed at first, then it fails. w[0] = &y; is also trying to modify the pointer, so it fails too.
On the other hand, (*w[0]) ++; (i.e. increment on the pointee) would be fine. And the following statements are fine too, because they're both modifying the objects pointed by the pointer, not the pointers.
*(w[0]) += 3; //passed.
*(w[0]) = 20; //passed.
It's a matter of operator precedence.
When you do *(w[0]) ++ you attempt to modify the pointer.
When you do *(w[0]) += 3 you modify the data pointed to by the pointer.
w is a const vector<int *>. The const qualifier is applied to the vector. Therefore, the corresponding const member function will be used for the operator[]:
const_reference operator[]( size_type pos ) const;
Since the vector is const-qualified and contains elements of type int * ( and not const int *), the type of the expression w[0] is int * const& (instead of const int *&). That is, it is a reference to a constant pointer to an int and not a reference to a pointer to a constant int: the constness is applied to the the pointer itself, not to the data being pointed.
By doing *(w[0]) += 3 you are not modifying the value of the pointer the vector returns (which is const), but the value this pointer is pointing to. Since this pointer is of type int * const (and not const int *), you can modify what it is pointing to, so it does work. However, doing w[0] = &y is performing an assignment on a constant pointer, so it does not compile.
const vector<T> lets you access its elements as T const & (i.e. const T &). In this case, T is int *, so this is int * const &, a const reference to a pointer that points to an int. The pointer is constant, but the int is not.
The type of the vector would have needed to be vector<int const *> (i.e. vector<const int*>) in which case the elements would be accessed via int const * const &.
Bottom line, constness is transitive with templates but not with pointers. And if you put pointers in templates, you get a bit of both behaviors.

const vector implies const elements?

Does const vector<A> mean that its elements are constas well?
In the code below,
v[0].set (1234); in void g ( const vector<A> & v )
produces the compiler error
const.cpp:28:3: error: member function 'set' not viable: 'this'
argument has
type 'const value_type' (aka 'const A'), but function is not marked const
Why?
But (*v[0]).set(1234); in void h ( const vector<A *> & v )
is OK for the compiler.
What's the difference between the versions?
// ...........................................................
class A {
private:
int a;
public:
A (int a_) : a (a_) { }
int get () const { return a; }
void set (int a_) { a = a_; }
};
// ...........................................................
void g ( const vector<A> & v ) {
cout << v[0].get();
v[0].set (1234);
} // ()
// ...........................................................
void h ( const vector<A *> & v ) {
cout << (*v[0]).get();
(*v[0]).set(1234);
} // ()
Yes, a const vector provides access to its elements as if they were const, that is, it only gives you const references. In your second function, it's not the objects of type A that are const, but pointers to them. A pointer being const does not mean that the object the pointer is pointing to is const. To declare a pointer-to-const, use the type A const *.
The first version
v[0].set (1234);
does not compile because it tries to change the vector's first element returned to it by reference. The compiler thinks it's a change because set(int) is not marked const.
The second version, on the other hand, only reads from the vector
(*v[0]).set(1234);
and calls set on the result of the dereference of a constant reference to a pointer that it gets back.
When you call v[0] on a const vector, you get back a const reference to A. When element type is a pointer, calling set on it is OK. You could change the second example to
v[0]->set(1234);
and get the same result as before. This is because you get a reference to a pointer that is constant, but the item pointed to by that pointer is not constant.
So a const object can only call const methods. That is:
class V {
public:
void foo() { ... } // Can't be called
void bar() const { ... } // Can be called
};
So let's look at a vector's operator[]:
reference operator[]( size_type pos );
const_reference operator[]( size_type pos ) const;
So when the vector object is const, it will return a const_reference.
About: (*v[0]).set(1234);
Let's break this down:
A * const & ptr = v[0];
A & val = *ptr;
val.set(1234);
Note that you have a constant pointer to variable data. So you can't change what is pointed at, but you can change the value that the pointer points at.
Yes, because std::vector is a value-type rather than a reference type.
To simplify things: An std::vector considers the values in its buffer as part of itself, so that changing them means changing the vector. This may be confusing if we only think of a vector as holding a pointer to an allocated buffer and the size: We don't change these two fields when we change elements in the buffer.
It's the opposite than for pointers, which are reference-types; if you change the pointed-to value you haven't changed the pointer itself.
The fact that std::vector is a value-type is a design choice - it's not something inherent in the C++ language. Thus, for example, the std::span class is also basically a pair of a pointer and a size, but an std::span can be const while you can still change the pointed-to elements. (There are other differences between spans and vectors.)