1

Why do const STL containers only return const_iterators?

For example both std::vector and std::list have the method begin overloaded as:

iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;

I thought I could still modify values of a const vector but not the vector itself. According to the standard library there is no difference between:

const std::vector<int>

and

const std::vector<const int>
barsdeveloper
  • 930
  • 8
  • 28
  • Node that this: http://stackoverflow.com/questions/27065617/const-vector-implies-const-elements Does not answer my question. – barsdeveloper Jun 18 '15 at 19:41
  • 2
    There is no difference between the "value" of a container and the container itself. Imagine if instead of vector, we were talking about an integer. What would it mean to say you could change the value of the integer but not the integer "itself"? – David Schwartz Jun 18 '15 at 19:44
  • possible duplicate of [What is the reason behind cbegin/cend?](http://stackoverflow.com/questions/12001410/what-is-the-reason-behind-cbegin-cend) – Cory Kramer Jun 18 '15 at 19:47
  • @DavidSchwartz Thank you, that makes sense. – barsdeveloper Jun 18 '15 at 20:03

2 Answers2

4

Suppose you have

iterator begin() const;

instead of

const_iterator begin() const;

Now, think what happens when you have

const vector<Foo> v;

You will be able to do something like

*v.begin() = other_foo;

which of course shouldn't be legal if you want to preserve logical const-ness. The solution is therefore to make the return type const_iterator whenever you invoke iterators on const instances.

The situation is similar to having const classes that have pointer members. In those cases, you may modify the data the pointer points to (but not the pointer itself), so logical const-ness is not preserved. The standard library took a step forward and disallowed these kind of modifications on standard containers via const overloads that return const_iterators.

vsoftco
  • 55,410
  • 12
  • 139
  • 252
  • But in a list or in a more complicated container (like a tree, that does not exist), the container itself remains const as only its node would be modificated by `*v.begin() = other_foo;`. – barsdeveloper Jun 18 '15 at 19:54
  • 1
    @biowep Depends on how you want your interface to behave. If you want `const Container` to be equivalent to `Container` (i.e., to preserve logical const-ness), then the solution is to use `const_iterator`s. If not, then of course it's up to you what you return as an iterator. Many many times it is a very good idea though to preserve logical const-ness, as otherwise you may end up with very subtle errors. In general when you write `const Container`, you really mean a container of which elements cannot be modified. – vsoftco Jun 18 '15 at 19:56
1

If you declare your vector as

const std::vector<int> foo;

Then the vector itself is const, meaning you cannot push_back, erase, etc. However, you can modify its elements

for (std::vector<int>::iterator it = foo.begin(); it != foo.end(); ++it)
{
    int& x = *it;
    x++;           // This is fine!
}

When you iterate over a vector, you are enforcing that the elements of the vector are const. So you can modify the vector by adding and removing things, but you may not modify the actual elements.

std::vector<Foo> values;  // Neither the vector nor its elements are const

for (std::vector<Foo>::const_iterator it = values.cbegin(), it != values.cend(); ++it)
{
    Foo const& foo = *it;         // I may not try to modify foo
    it->CallToNonConstMethod();   // Can't do this either
}
Cory Kramer
  • 114,268
  • 16
  • 167
  • 218
  • And how would that thing compile, if you assign const_iterator to an iterator (in the for-loop declaration)? That is not a legal conversion. – MkjG Dec 23 '19 at 17:12