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>
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.
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
}
Related
given the following code:
#include <set>
using std::set;
class Pool {
set<int> s;
public:
typedef typename set<int>::iterator Iterator;
const Iterator& begin() const {
return s.begin(); //** error
}
};
Why I get the following error (I understand the meaning of the error, but, I don't understand why I get it in this case)?
returning reference to temporary [-Werror=return-local-addr]
How can I fix it?
The set<...>::begin function returns its iterator by value. Since you do not store that value anywhere, it's a temporary value, and you can't really have references to temporary values.
The simple solution is for your function to also return by (non-const) value.
const Iterator& begin() const {
return s.begin(); //** error
}
is illegal because the result of s.begin() is a temporary object and therefore you can't return a reference to it. Maybe if you look at this equivalent code it will be clearer?
const Iterator& begin() const {
Iterator it = s.begin();
return it; //** error
}
Returning a const reference to an iterator doesn't make much sense anyway as you wont be able to move the location that the iterator points to. You should always return iterators by value, this way the caller is free to make copies of the iterator and modify their location. For example if you could get your code to compile the following calling code would not work:
Pool p;
const Pool::Iterator& it = p.begin();
++it; //error it is constant
The user could fix their code by copying your returned reference into a new object:
Pool p;
Pool::Iterator it = p.begin();
++it; //no error
As your users wont be able to use the reference you can't return its better just to return by value:
const Iterator begin() const {
return s.begin();
}
Note that std::set is not like most other containers and doesn't allow modification of the values via its iterators: https://en.cppreference.com/w/cpp/container/set/begin
Because both iterator and const_iterator are constant iterators (and may in fact be the same type), it is not possible to mutate the elements of the container through an iterator returned by any of these member functions.
Is this:
auto& ref1 = *it++;
ref1 = expression; // (1)
one of the required semantics of a forward iterator? And what about a random access iterator?
auto& ref1 = it[3];
ref1 = expression; // (2)
According to cppreference, a forward iterator is required to:
// Return reference, which equals to (const) value_type &
*it++ === value_type&
and for a random access iterator:
it[n] === *(it + n)
which is the same situation, which means that in both situations you are dereferencing a temporary (the iterator). In my case, my iterator stores by copy an index which allows to access a container which doesn't provide direct access to the stored elements, only through the index.
That works fine:
*it++ = value;
since the temporary copy of it has sentence scope.
But in this case:
type& val = *it++;
val = 3;
we get undefined behaviour, since the copy is already destroyed in the second line.
In my situation, I have a QModelIndex wrapper to get data/save from/to a QAbstractItemModel. The model only gives you copies of the QVariants stored on the model.
My wrapper class (the value_type with the operator= overloaded) saves an instance of a QModelIndex (to manipulate the model), and the iterator an instance of that wrapper. So, if the iterator is destroyed, the wrapper and the index too.
I think I can solve both problems as far as lines (1) and (2) don't need to be supported.
NOTE: My implementation is more or less like that (simplified):
// The value type
struct index
{
QModelIndex qidx;
index& operator=(QVariant const& val)
{
if (qidx.isValid())
qidx.model()->setData(qidx, val);
return *this;
}
};
// Private class actually. The "movements" cannot be done
// over the value type because it will cause, in functions
// returning references to the value type, to increase the chaos.
// So, I make the index points to different model items using
// this class.
struct index_manipulator
{
QModelIndex& qidx;
void move(int rows, int cols)
{
if (qidx.isValid())
qidx = qidx.model()->index(qidx.row() + rows,
qidx.column() + cols);
}
};
struct index_safe_ref
{
mutable index idx;
operator index&() const { return idx; }
};
struct my_row_it
{
index idx;
index_manipulator manip = {idx.qidx};
my_row_it(QAbstractItemModel* m, int col)
: idx(m ? m->index(0, col) : QModelIndex())
{}
index& operator*() const { return idx; }
my_row_it operator++(int) const
{
auto copy = it;
manip.move(1, 0);
return copy;
}
index_safe_ref my_row_it::operator[](difference_type n) const
{
auto it = it + n; // Operator+ is over there.
return { it.idx };
}
};
A stashing iterator (that is, an iterator that returns a reference to something within itself) is never a valid forward iterator.
Iterators in general must be CopyConstructible ([iterator.iterators]/2.1, which requires, among other things, that a copy of the iterator be equivalent to the original. It follows that a forward iterator and its copy must necessarily compare equal, and [forward.iterators]/6 requires that that for two equal dereferenceable iterators a and b, *a and *b must be bound to the same object, which cannot be satisfied for stashing iterators.
If you need to ignore a requirement, I suggest ignoring the one that says reference must be an actual reference type, turning your stashing iterator into a proxy iterator. There's established practice for that in the standard library (vector<bool>::iterator) and any breakage is likely to be a loud compile-time error, rather than silent runtime mischief.
This is covered by a general statement about iterators:
Destruction of an iterator may invalidate pointers and references previously obtained from that iterator.
ยง24.2.1/9 N3337
But, as T.C. points out in the other answer your iterator cannot be a valid forward iterator (or anything more strict then) if you return a reference to an object contained within the iterator object.
I see two solutions: Return that index object by value, or return a reference to a heap allocated index object.
As a note, an input iterator needs to support this:
value_type temp = *iterator++; // or *iterator; ++iterator;
// use temp
So in your case this must work (but should as far as I can see):
index temp = *iterator++;
temp = expression.
This is not the same as line (1) because above code involves a conversion to the value_type (and not a reference to it).
This works perfectly :
list<int> l;
list<int>::const_iterator it;
it = l.begin();
list<int>::iterator it2;
it2 = l.begin();
What I don't get is how the list "knows" that it must return the iterator begin() version or the const_iterator begin() const one.
I'm trying to implement iterators for my container (a trie) and I'm running into this problem. Isn't C++ supposed not to handle differentiation by return type (except when using weird tricks)?
Here is some code and the compiler error I get :
My Trie<T> is a templated trie that can contain any type. I have a Trie<int>::iter non-const iterator and a Trie<int>::const_iter const iterator. iter begin() and const_iter begin() const are declared (and defined) in the Trie class.
Trie<int> t;
Trie<int>::const_iter it;
it = t.begin();
Error :
../test/trie.cpp:181: error: no match for 'operator=' in 'it = Trie<T>::begin() [with T = int]()'
[..path..]/Trie.h:230: note: candidates are: Trie<int>::const_trie_iterator& Trie<int>::const_trie_iterator::operator=(const Trie<int>::const_trie_iterator&)
So, I believe the non-const version of begin is not used.
I contemplated creating an operator=(const Trie<T>::const_trie_iterator&) method for the non-const iterator but I don't see that in the STD lib and I would have to const_cast the iterator. What should I do?
In standard containers, a non-const iterator is implicitly convertible to a const_iterator. The type returned is based solely on the const-ness of the object/reference on which begin() was called, which in your case would be iterator, there is a conversion that allows the later assignment.
In particular in the 23.2.1 General Container Requirements, table 96, it says that X::iterator must be convertible to X::const_iterator.
list knows which kind of iterator to return because there are two begin methods defined, one for when the list is const, and one for when it isn't. The declarations might look something like this:
template<class T>
class list {
public:
iterator<T> begin();
const_iterator<T> begin() const;
}
In the following example, the first, non-const iterator would be returned, because the list isn't const:
void doSomething(list<int> &myList) {
iterator<int> i = myList.begin();
...
}
In the next example, the list is declared as const, so the second version of begin that returns a const_iterator would be used instead:
void doSomethingElse(const list<int> &myList) {
const_iterator<int> i = myList.begin();
....
}
Of course, an iterator can always be cast to a const_iterator, so you could declare i to be a const_iterator in either example, but if you try to declare i to be an iterator in the second example you'll get an error since a const_iterator can not be implicitly cast as an iterator.
I have a class called PointList, which holds a vector of Point * objects as its main data. I want to iterate over the points the same way you would a vector, kind of like this:
for (vector<Point *>::iterator it = point_list->begin(); it != point_list->end(); ++it)
Clearly the begin() and end() functions I write can just return the vector's begin/end functions that they hold, but what is the return type of these functions?
If I have understood the question right, the answer is right in your question. You already use the return value and type of begin and end in your piece of code.
vector<Point *>::iterator it = point_list->begin();
clearly, it holds the return value of begin() and its type is well known:
vector<Point *>::iterator
By the way, a little off-topic - why point_list is pointer to vector, not an object? And second, why it's called list, as it's vector? Use vector, or array, or sequence, but not list, as it could be misleading. list is a STL container, different from vector.
Their return type would be vector<Point *>::iterator.
You should copy the container interface, by providing two iterator types, three begin functions and three end functions. The most obvious iterator types to use are taken straight from the vector:
struct PointList {
typedef std::vector<Point*>::iterator iterator;
typedef std::vector<Point*>::const_iterator const_iterator;
iterator begin();
const_iterator begin() const;
const_iterator cbegin() const;
iterator end();
const_iterator end() const;
const_iterator cend() const;
};
cbegin() and cend() are new to C++11, they aren't in C++03. The idea is that since they don't have a non-const overload, the user can call them on a non-const container in preference to messing about with a conversion.
Since the underlying storage is in a vector, you might also consider providing (c)rbegin() and (c)rend(). In fact to implement the standard container interface you'd have to, since your iterator type is random-access. If you don't want to do that (perhaps because some future implementation of this class will not necessarily use a vector, but some other container underneath), then there is an argument for wrapping the vector's iterator in a class of your own just as you've wrapped the vector in a class of your own. That's extra work that's only needed if you need to prevent users from relying on properties of the iterator that could disappear in future implementations. You might not care about this in an internal API, more so in a published one.
They are of type vector<Point *>::iterator just like your it object. But why do you want to iterate your data outside of the container object? That would be violation of encapsulation no?
What is the difference between a const_iterator and an iterator and where would you use one over the other?
const_iterators don't allow you to change the values that they point to, regular iterators do.
As with all things in C++, always prefer const, unless there's a good reason to use regular iterators (i.e. you want to use the fact that they're not const to change the pointed-to value).
They should pretty much be self-explanatory. If iterator points to an element of type T, then const_iterator points to an element of type 'const T'.
It's basically equivalent to the pointer types:
T* // A non-const iterator to a non-const element. Corresponds to std::vector<T>::iterator
T* const // A const iterator to a non-const element. Corresponds to const std::vector<T>::iterator
const T* // A non-const iterator to a const element. Corresponds to std::vector<T>::const_iterator
A const iterator always points to the same element, so the iterator itself is const. But the element it points to does not have to be const, so the element it points to can be changed.
A const_iterator is an iterator that points to a const element, so while the iterator itself can be updated (incremented or decremented, for example), the element it points to can not be changed.
Unfortunaty, a lot of the methods for the STL containers takes iterators instead of const_iterators as parameters. So if you have a const_iterator, you can't say "insert an element before the element that this iterator points to" (saying such a thing is not conceptually a const violation, in my opinion). If you want do that anyway, you have to convert it to a non-const iterator using std::advance() or boost::next(). Eg. boost::next(container.begin(), std::distance(container.begin(), the_const_iterator_we_want_to_unconst)). If container is a std::list, then the running time for that call will be O(n).
So the universal rule to add const wherever it is "logical" to do so, is less universal when it comes to STL containers.
However, boost containers take const_iterators (eg. boost::unordered_map::erase()). So when you use boost containers you can be "const agressive". By the way, do anyone know if or when the STL containers will be fixed?
Minimal runnable examples
Non-const iterators allow you to modify what they point to:
std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
*it = 1;
assert(v[0] == 1);
Const iterators don't:
const std::vector<int> v{0};
std::vector<int>::const_iterator cit = v.begin();
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;
As shown above, v.begin() is const overloaded, and returns either iterator or const_iterator depending on the const-ness of the container variable:
How does begin() know which return type to return (const or non-const)?
how does overloading of const and non-const functions work?
A common case where const_iterator pops up is when this is used inside a const method:
class C {
public:
std::vector<int> v;
void f() const {
std::vector<int>::const_iterator it = this->v.begin();
}
void g(std::vector<int>::const_iterator& it) {}
};
const makes this const, which makes this->v const.
You can usually forget about it with auto, but if you starting passing those iterators around, you will need to think about them for the method signatures.
Much like const and non-const, you can convert easily from non-const to const, but not the other way around:
std::vector<int> v{0};
std::vector<int>::iterator it = v.begin();
// non-const to const.
std::vector<int>::const_iterator cit = it;
// Compile time error: cannot modify container with const_iterator.
//*cit = 1;
// Compile time error: no conversion from const to no-const.
//it = ci1;
Which one to use: analogous to const int vs int: prefer const iterators whenever you can use them (when you don't need to modify the container with them), to better document your intention of reading without modifying.
Use const_iterator whenever you can, use iterator when you have no other choice.
(as others have said) const_iterator doesn't allow you modify the elements to which it points, this is useful inside of const class methods. It also allows you to express your intent.
ok Let me explain it with very simple example first without using constant iterator
consider we have collection of random integers collection "randomData"
for(vector<int>::iterator i = randomData.begin() ; i != randomData.end() ; ++i)*i = 0;
for(vector<int>::const_iterator i = randomData.begin() ; i!= randomData.end() ; ++i)cout << *i;
As can be seen for writing/editing data inside collection normal iterator is used but for reading purpose constant iterator has been used . If you try using constant iterator in first for loop you will get error . As a thumb rule use constant iterator to read data inside collection .