const vector implies const elements? - c++

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.)

Related

How are templates work so that the classes are properly generated by the compiler to work with both pointer types and non pointer types?

Given
int i = 42;
int* p = &i;
we cannot do
const int * & d = p;
In my current understanding, this is because const int * & d can be read as "d is a reference of a pointer to an int that is a const" and p is "a pointer to an int", which means the RHS needs to be turned into a temporary being "a pointer to an int that is a const" and that cannot be binded directly with a reference (as on the LHS). We would need to have, say, const int* const& d = p;. Possible references: 1, 2.
That said, consider the following toy code:
template<typename T>
class X
{
public:
void fun(const T& d) { }
};
At first, I thought if I do
X<int*> x;
x.fun(p);
The method generated by the compiler would be void fun(const int*& d), and I would get an error due to the above reason.
However, there is no error and it works "as expected" (by that I mean, we always have d itself as the const no matter T is replaced by pointer types or by non-pointer types).
My current guess is that, for the above scenario, the method generated by the compiler is something like void fun(int* const& d).
May I ask if my guess is correct? How are templates work so that the classes generated by the compiler work with pointers "as expected"?
Your guess is correct.
For const T, const is qualified on type T directly. Given T is int* (non-const pointer to non-const int), const T results in const pointer, i.e. int* const (const pointer to non-const int) but not const int* (non-const pointer to const int).

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?

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.

Why const Ref on non-const pointer is interpreted as const pointer?

Why const PointerToNonConst& value is understood as A* const for the code below:
using PointerToNonConst = A*; // or typedef A* PointerToNonConst;
const PointerToNonConst& value; // compiled and understood as `A* const`
I have expected it to be read-only pointer instead on constant pointer.
BTW here is a use-case, why you may meet this in your code:
class A
{
public:
void callOnNonConstMethodIsValid()
{
// change the object here
}
}
std::vector<A*> vect;
for (const auto& elem : vect)
{
elem->callOnNonConstMethodIsValid(); // no error
}
typedef and using are not like #define. It is not text replacement. When you make a type alias with typedef or using, that name is then treated as a single unit. When you apply const to it, it applies the type as a whole, at top level. You cannot apply it to inner components of that type.
Given
using NonConstPtr = A*; // or typedef A* NonConstPtr;
The declaration
const NonConstPtr& value = <initializer>;
is equivalent to
A* const& value = <initializer>;
value is a reference to a const pointer to an object of type A.
It is not equivalent to:
const A*& value = <initializer>;
where value is a reference to a pointer to const A.
This is one of the cases where it makes more sense to use the const keyword after the type.
NonConstPtr const& value = <initializer>;
If you do that, it might make more sense why the first interpretation is correct.

Const placement to stop editing of pointer data with boost shared_ptr

So Wikipedia tells me (correctly i believe) that to stop the editing of the data of a pointer and the pointer itself that I should do this:
void function(int const * const var)
Is this the same as this function:
void function(const int * const var)
And in that case why is it allowed? Because I know that you cant do this because of duplicate const compile error:
void function(const int const * const var)
I essentially want to do the same with a boost pointer. Would I do this:
void function(const boost::shared_ptr<int> const var)
And how would this affect my ability to loop over say a shared pointer to a vector? Could I do this with that guard:
void function(const boost::shared_ptr<std::vector<int>> const var)
{
for (unsigned int i = 0; i < var->size(); ++i)
{
std::cout << var[i];
}
}
Adition: After Brian's answer
So if I create a a pointer like this:
boost::shared_ptr<vector<int>> lala
And i use it in this function:
function (const boost::shared_ptr<std::vector<const int>> var)
will that work?
Yes, int const * const var is the same as const int * const var. You can put const before or after the type it modifies, for "historical reasons". See http://www.stroustrup.com/bs_faq2.html#constplacement
For a smart pointer object, you indeed cannot do
const boost::shared_ptr<int> const
because both consts modify the same type. Instead, you want
const boost::shared_ptr<const int>
The first const prevents the pointer itself from being modified (i.e., reassigned to point to another int) and the const in the template parameter tells the object's operator* to return a const int&, which prevents modification of the int pointed to.
This does not prevent iteration over a vector or other container in the manner you have described, for the same reason why you can still iterate over a const vector normally.
Edit in response to question edit:
This works:
void f(const boost::shared_ptr<const std::vector<int> > var);
// ...
boost::shared_ptr<vector<int> > lala;
f(lala);
The first const in the parameter doesn't affect parameter passing at all; it only tells the function itself not to modify the parameter. The reason why we can add a const in the template parameter is that a boost::shared_ptr<T> can be initialized from boost:shared_ptr<U> where T and U are not necessarily the same type, as long as U* is implicitly convertible to T*. If T is the same as U except with greater cv-qualification, as in this case, the conversion is possible.
Don't do std::vector<const int>. I'm fairly sure that's not legal. (At least I've gotten multiple screens of compilation errors every time I've tried it.)
const before or after the int is the same so:
int const * var
and
const int * var
are the same and mean the value pointed to cannot be changed
const after the * means that the pointer cannot be reassigned.
If I understand correctly, you'd like to make the vector const. If that's the case the syntax would be this:
void function(const boost::shared_ptr<const std::vector<int>>& var)
The smart pointer is passed by const reference because it's cheaper than passing the smart pointer by value and has the same effect. The object pointed to by the smart pointer is immutable by declaring the type it points to as const.
You've correctly reasoned that const shared_ptr<Foo> doesn't make Foo a const object. This "loophole" is described on wikipedia. Instead, you need to change the pointer type stored by boost::shared_ptr. This can be done in the template argument itself:
void function(const boost::shared_ptr<const std::vector<int>>& var)
boost::shared_ptr has a copy-constructor that allows for a const-type to be copied from a non-const-type. The opposite should not be possible.
Before, or after?
The below two lines are semantically equivalent, both declare a pointer which value cannot be changed, that refers to an int that cannot be changed.
int const * const p1 = ...;
const int * const p2 = ...;
The const keyword binds to whatever is directly to the left, unless there's nothing to the left, in which case it will hug whatever is on the right.
The more const, the better?
typedef boost::shared_ptr<int> shared_int_ptr;
const shared_int_ptr const p3; // ill-formed
The above typedef is provided to make it easier to see that boost::shared_ptr<int> is a single name, and therefore we cannot add const on both sides; duplicate consts are (as you mentioned) not legal C++.
Just applying one wouldn't be sufficient either, since that would make the wrapper const, but not the internal object that it is referring to (the int).
Previously we wrote that boost::shared_ptr should wrap around an int, but since we want to make the wrapped type const, well.. let's wrap the shared_ptr around what we want:
void func (const boost::shared_ptr<const int> foo);
In the above func is not able to modify foo, nor the int referred to by foo.