With a container class, such as std::vector
, there are two different concepts of constant-ness: that of the container (i.e. its size) and that of the elements. It seems that std::vector
confuses these two, such that the following simple code won't compile:
struct A {
A(size_t n) : X(n) {}
int&x(int i) const { return X[i]; } // error: X[i] is non-const.
private:
std::vector<int> X;
};
Note that even though the data members (the three pointers to the begin&end of the data and the end of the allocated buffer) of std::vector
are not changed by a call to its operator[]
, this member is not const
-- isn't this a strange design?
Note also that for raw pointers, these two concepts of constant-ness are neatly separated, such that the corresponding raw-pointer code
struct B {
B(size_t n) : X(new int[n]) {}
~B() { delete[] X; }
void resize(size_t n); // non-const
int&x(int i) const { return X[i]; } // fine
private:
int*X;
};
works just fine.
So what is the correct/recommended way to deal with this when using std::vector
(without using mutable
)?
Is a const_cast<>
as in
int&A::x(int i) const { return const_cast<std::vector<int>&>(X)[i]; }
deemed acceptable (X
is known to be non-const
, so no UB here)?
EDIT just to prevent further confusion: I do want to modify the elements, i.e. the contents of the container but not the container itself (the size and/or memory location).
const member functions may be invoked for const and non-const objects. non-const member functions can only be invoked for non-const objects. If a non-const member function is invoked on a const object, it is a compiler error.
Once you have a const object, it cannot be assigned to a non-const reference or use functions that are known to be capable of changing the state of the object. This is necessary to enforce the const-ness of the object, but it means you need a way to state that a function should not make changes to an object.
const prevents the variable to be assigned to another value. We could say it makes the pointer immutable, but it doesn't make the value immutable too! const arr = [1, 2, 3]arr. push(4) // this is totally finearr = ['foo', 'bar'] // TypeError: Assignment to constant variable.
How to call a non-const method from a const method? You should not. You might run into undefined behaviour if you cast away the const-ness of this , using const_cast . The usage of const_cast will shut the compiler's mouth up, but that isn't a solution.
C++ only supports one level of const
. As far as the compiler
is concerned, it is bitwise const: the "bits" actually in the
object (i.e. counted in sizeof
) cannot be modified without
playing games (const_cast
, etc.), but anything else is fair
game. In the early days of C++ (late 1980s, early 1990s) there
was a lot of discussion as to the design advantages of bitwise
const vs. logical const (also known as Humpty-Dumpty const,
because as Andy Koenig once told me, when the programmer uses
const
, it means exactly what the programmer wants it to mean).
The consensus finally coalesced in favor of logical const.
This does mean that authors of container classes have to make a choice. Are the elements of the container part of the container, or not. If they're part of the container, then they cannot be modified if the container is const. There's no way to offer a choice; the author of the container has to choose one or the other. Here too, there seems to be a consensus: the elements are part of the container, and if the container is const, they cannot be modified. (Perhaps the parallel with C style arrays played a role here; if a C style array is const, then you cannot modify any of its elements.)
Like you, I've encountered times when I wanted to forbid
modification of the size of the vector (perhaps to protect
iterators), but not of its elements. There are no really
satisfactory solutions; the best I can think of is to create
a new type, which contains a mutable std::vector
, and provide
forwarding functions which correspond to the meaning of const
I need in this specific case. And if you want to distinguish
three levels (completely const, partially const, and non-const),
you'll need derivation. The base class only exposes the
completely const and partially const functions (e.g. a const
int operator[]( size_t index ) const;
and int operator[](
size_t index );
, but not void push_back( int );
); the
functions which allow insertion and removal of an element are
only exposed in the derived class. Clients which shouldn't
insert or remove elements are only passed a non-const reference
to the base class.
Unfortunately, unlike pointers, you can't do something like
std::vector<int> i;
std::vector<const int>& ref = i;
That's why std::vector
can't disambiguate between the two kinds of const
as they might apply, and it has to be conservative. I, personally, would choose to do something like
const_cast<int&>(X[i]);
Edit: As another commenter accurately pointed out, iterators do model this dichotomy. If you stored a vector<int>::iterator
to the beginning, you could then de-reference it in a const method and get back a non-const int&
. I think. But you'd have to be careful of invalidation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With