Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

const vs non-const of container and its content

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

like image 660
Walter Avatar asked Feb 11 '13 16:02

Walter


People also ask

Can const methods be used by non-const objects?

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.

Why can't you assign a const reference to a non-const reference?

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.

What is the effect of const?

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 do you call a non-const method from the const method?

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.


2 Answers

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.

like image 70
James Kanze Avatar answered Sep 28 '22 04:09

James Kanze


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.

like image 36
Puppy Avatar answered Sep 28 '22 04:09

Puppy