1

I am defining iterators for a custom container. The iterators implement both InputIterator and OutputIterator concepts.

What types should be used for iterator::reference and const_iterator::reference? And what types should be returned by operator* for iterator, const iterator and const_iterator?

In the iterator case, should there be two definitions of operator*? as follows (T is the contained value type):

template<typename T>
class iterator {
public:
using reference = T&;
...
T& operator*() { return ...reference to the underlying storage... }
const T& operator*() const { return ...const reference to the underlying storage... }
...
};

Or just one:

T& operator*() const { return ...reference to the underlying storage... }

Put another way, does a const iterator allow mutable access to the underlying container?

For the const_iterator case, am I correct to guess that the following is correct?

template<typename T>
class const_iterator {
public:
using reference = const T&;
...
const T& operator*() const { return ...reference to the underlying storage... }
...
};

Or should the definition of const_iterator::reference be T& without const?

In addition to a complete answer, I would appreciate a reference to an authoritative source where I can look up similar information in future.

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
Ross Bencina
  • 3,822
  • 1
  • 19
  • 33
  • A `const iterator` will not be pre-incrementable as it is `const`. So kind of would defeat the purpose – Curious Jun 10 '17 at 05:20
  • @Curious I'm not sure about that. For example, an iteration loop uses a non-`const` iterator but passes it as a parameter to a function that takes a `const iterator&`. – Ross Bencina Jun 10 '17 at 05:23
  • Can you give an example of what you mean? you can always convert a value to its `const` counterpart – Curious Jun 10 '17 at 05:24
  • @Curious, the question is quite clear: if you have a `const iterator i` what is the type of `*i*`? – Ross Bencina Jun 10 '17 at 05:26
  • I don't think I follow what you mean sorry.. `*i` for a regular `iterator` and not a `const_iterator` evaluates to a non const reference to the underlying type usually. But you already knew that? – Curious Jun 10 '17 at 05:27
  • @curious, there is a third case: when `i` is of type `const iterator`. the question is: what is the type of `*i` in that case. – Ross Bencina Jun 10 '17 at 06:47
  • Is that the question you wanted to ask in the question above? If so I will write an answer to explain – Curious Jun 10 '17 at 06:49
  • @Curious the question contains multiple specific details that need to be answered, they all center around the question of the definition of `operator*`, `iterator::reference` and `const_iterator::reference`. If you do not feel that the question is clear enough to answer, I think it's better if you don't try. – Ross Bencina Jun 10 '17 at 06:51
  • Ok, I will write up an answer – Curious Jun 10 '17 at 06:51
  • Done, take a look and feel free to post followups if something is still unclear – Curious Jun 10 '17 at 07:09

2 Answers2

3

Put another way, does a const iterator allow mutable access to the underlying container?

Yes. A const iterator is not an iterator to const; what is const is itself, not the object it points to. (It's analogous to the const pointer; i.e. T* const or const std::unique_ptr<T>.)

Or should the definition of const_iterator::reference be T& without const?

It should return const T&. const_iterator is an iterator to const; that means what it points to should not be allowed to be modified. (It's analogous to the pointer to const; i.e. const T* or std::unique_ptr<const T>.)

songyuanyao
  • 169,198
  • 16
  • 310
  • 405
1

What types should be used for iterator::reference and const_iterator::reference?

If you want to find out what type you should assign to your iterator traits (iterator::reference, iterator::pointer, etc) you can try and mimic what std::vector does, the following program when compiled will tell you everything you need to know (live demo)

#include <vector>

template <typename...>
struct WhichType;

int main() {
    auto vec = std::vector<int>{};
    WhichType<typename decltype(vec.begin())::difference_type>{};
    WhichType<typename decltype(vec.begin())::value_type>{};
    WhichType<typename decltype(vec.begin())::pointer>{};
    WhichType<typename decltype(vec.begin())::reference>{};
    WhichType<typename decltype(vec.begin())::iterator_category>{};

    WhichType<typename decltype(vec.cbegin())::difference_type>{};
    WhichType<typename decltype(vec.cbegin())::value_type>{};
    WhichType<typename decltype(vec.cbegin())::pointer>{};
    WhichType<typename decltype(vec.cbegin())::reference>{};
    WhichType<typename decltype(vec.cbegin())::iterator_category>{};
}

what types should be returned by operator* for iterator, const iterator and const_iterator?

operator* for regular non const_ iterators should return a mutable reference to the underlying type, there is no ambiguity there if the semantics of the container allow that. For example std::map iterators return const references to the key type, because the key should not be modified in a std::map (see https://stackoverflow.com/a/32510343/5501675)

const iterator conveys the same meaning that T* const, const std::unique_ptr<int> do, they represent const pointers to non const data. So you can use the pointer to modify the data they point to, but you cannot modify the pointer itself. In this case the iterator acts like a pointer. So you cannot modify the iterator, but you can modify what the iterator points to (live demo)

As a consequence of the iterator being const you cannot call the operator++() (or the preincrement operator) to advance the iterator to the next position. You cannot do this because the method is not const. Because of this most code never uses iterators that are const themself. You are better off using a const reference to the underlying type.

const_iterator is defined to solve the issue of iterators returning const references to the underlying type. So they return const references to the underlying type. Live demo

As to what typedefs it should have, the first code example when compiled should tell you that

does a const iterator allow mutable access to the underlying container?

Yes, if the container does. For example std::vector<int> does, whereas std::map<int, int> does not allow mutable access to its key types

Or should the definition of const_iterator::reference be T& without const?

See the live demo above, when you compile the code you will see that it is a const T&

Curious
  • 20,870
  • 8
  • 61
  • 146
  • A clear and direct answer to the question would be a better than "the following program when compiled will tell you everything you need to know". On the other hand, the code example provides a partial answer to my question about authoritative sources, so it's of clear value. (Although I'd prefer citations to the appropriate standard.) – Ross Bencina Jun 12 '17 at 23:56