views:

140

answers:

5

From Effective C++, Item 3

/* case1 */ const std::vector<int>::iterator i  // i  acts like a T* const
/* case2 */ std::vector<int>::const_iterator ci // ci acts like a const T*

To remember how const applies, I used to remember the following from this article

Basically ‘const’ applies to whatever is on its immediate left (other than if there is nothing there in which case it applies to whatever is its immediate right).

When I read the Item 3 in the book first, I expected it to be the other way round in case1 and case2.

Should I treat this case as an exception? Or is there some deeper level understanding that I am missing?

+1  A: 

The rule quoted is absolutely true of the const keyword. However, when dealing with iterators, the const_iterator class is merely named so (it could equally be readonly_iterator), so the rule about the const keyword does not apply.

You can however declare case 1 like this:

std::vector<int>::iterator const i

just like you can specify either const int x or int const x.

The type an iterator points to is also specified in the template parameter of its container, so the ordering is different to declaring ordinary variables.

I can't see that there's any particular rule of thumb to follow here - the comments in your post are the right idea, you just need to learn to treat const_iterator as const T* and so on.

AshleysBrain
+4  A: 

You can think of it being as if the iterators were typedefed like so:

typedef T* iterator;
typedef const T* const_iterator;

When you add const to either of these, it applies at the top level, that is, to the pointer itself, not to the object pointed to, so the following equivalencies hold:

const iterator it; // is the same as:
T* const it;

const const_iterator it; // is the same as:
const T* const it;

They are not an exception; this is how all typedefs work.

James McNellis
+7  A: 

The rule works as advertised.

const std::vector<int>::iterator i

The item on the immediate right is iterator: the iterator is immutable. You cannot assign the iterator to point to different items in the vector, you cannot increment it, it always points to the item it is initialized to. You can change the item being pointed to, though.

This is rarely the desired behavior, which is why the const_iterator typedef exists.

std::vector<int>::const_iterator ci

The iterator can be moved around, but the item pointed to cannot be modified. This is pretty much always what you want--you want to iterate over a vector but not be able to modify its contents.

There's no const keyword here so you can't use the rule to figure it out. For this one you just have to understand what const_iterator is documented to do.

John Kugelman
One place where I have found the first case to be useful is saving the output of a search, say from `std::find_if`. `const std::vector<int>::iterator i = std::find_if(...);` says that I want to remember the result of the search, and I don't want to inadvertently change that result.
SCFrench
+1  A: 

I think that the const_iterator is confusing you. Here is an example with simpler typedefs.

typedef int* Type;
typedef const int* ConstType;

int main() {
  const Type a; // int * const
  ConstType b; // const int *
}
Bertrand Marron
A: 

The const in case 1 applies to the iterator, so it makes the iterator itself const. It doesn't have any effect on the item the iterator is "pointing to". (This is like in T* const, where the pointer is const, not the pointed-to T)

In case 2, the const is just part of the name const_iterator, so it can't really be deduced what is const there. The class could just have a bad name and there could be nothing const about it at all. In this case however, the const_iterator doesn't allow modification of it's target element, because it is defined like that in the standard library.

sth