Item 26 from Scott Mayers's "Effective STL" is labeled "Prefer iterator to const_iterator, reverse_iterator and const reverse iterator".
The reasoning is that some forms of insert() and erase() require exactly iterator and converting from the other types is tedious and error-prone. Furthermore, comparing iterator and const_iterator could be problematic, depending on the STL implementation.
The book was released at 2001. Is the advice in Item 26 still valid with the current state of gcc?
The C++14 standard (N3936) guarantees that iterator and const_iterator are freely comparable (§23.2.1 [container.requirements.general]/p7):
In the expressions
i == j
i != j
i < j
i <= j
i >= j
i > j
i - j
where i and j denote objects of a container’s iterator type,
either or both may be replaced by an object of the container’s
const_iterator type referring to the same element with no change in
semantics.
In addition, the container member functions take const_iterator parameters as of C++11 (§C.2.13 [diff.cpp03.containers] - as might be inferred from the tag, this is a change from C++03):
Change: Signature changes: from iterator to const_iterator parameters
Rationale: Overspecification. Effects: The signatures of the following member functions changed from taking an iterator to taking
a const_iterator:
insert(iter, val) for vector, deque, list, set, multiset, map, multimap
insert(pos, beg, end) for vector, deque, list, forward_list
erase(iter) forset,multiset,map,multimap`
erase(begin, end) forset,multiset,map,multimap`
all forms of list::splice
all forms of list::merge
The container requirements have been similarly changed to take const iterators. In addition, it is easy to obtain the underlying iterator from a std::reverse_iterator via its .base() member function. Thus, neither of the concerns noted in the question should be an issue in a conforming compiler.
The advice has been reversed, as can be seen from Item 13 of the upcoming Effective Modern C++ which is titled:
Prefer const_iterators to iterators
The reason is that C++11 and C++14 add several tweaks that make const_iterators a lot more practical:
C++11 adds
member functions cbegin() and cend() (and their reverse counterparts) for all Standard Library containers
member functions using iterators to identify positions (e.g. insert(), erase()) now take a const_iterator instead of an iterator
C++14 completes that by adding non-member cbegin() and cend() (and their reverse counterparts)
Related
https://en.cppreference.com/w/cpp/container/vector/insert
Cppreference shows: iterator insert( const_iterator pos, const T& value ); and four other overloads.
But why the parameter is const_iterator but not iterator?
Whether or not the iterator is const doesn't matter, since the container is the thing being modified (and insert is not a const-qualified member function), not the passed in iterator.
And this just makes it easier to use. A non-const iterator is convertible to a const_iterator (but not the other way around) so you can still easily use an iterator.
A somewhat relevant paper: https://wg21.link/N2350
Consider the following code:
#include <vector>
#include <iostream>
int main()
{
std::vector<int> vec{1,2,3,5};
for(auto it=vec.cbegin();it!=vec.cend();++it)
{
std::cout << *it;
// A typo: end instead of cend
if(next(it)!=vec.end()) std::cout << ",";
}
std::cout << "\n";
}
Here I've introduced a typo: in the comparison I called vec.end() instead of vec.cend(). This appears to work as intended with gcc 5.2. But is it actually well-defined according to the Standard? Can iterator and const_iterator be safely compared?
Surprisingly, C++98 and C++11 didn't say that you can compare a iterator with a const_iterator. This leads to LWG issue 179 and LWG issue 2263. Now in C++14, this is explicitly permitted by § 23.2.1[container.requirements.general]p7
In the expressions
i == j
i != j
i < j
i <= j
i >= j
i > j
i - j
where i and j denote objects of a container's iterator type, either or
both may be replaced by an object of the container's const_iterator
type referring to the same element with no change in semantics.
See §23.2.1, Table 96:
X::iterator
[...]
any iterator category that meets the forward iterator requirements.
convertible to X::const_iterator
So, yes, it is well-defined.
Table 96 in the C++11 Standard, in section 23.2.1, defines the operational semantics of a.cend() for any container type X (including std::vector) as follows:
const_cast<X const &>(a).end()
So the answer is yes because by this definition cend() refers to the same element/position in the container as end(), and X::iterator must be convertible to X::const_iterator (a requirement also specified in the same table(*)).
(The answer is also yes for begin() vs. cbegin() for the same reasons, as defined in the same table.)
(*) It has been pointed out in comments to other answers that convertibility does not necessarily imply that the comparison operation i1==i2 will always work, e.g. if operator==() is a member function of the iterator type, the implicit conversion will only be accepted for the righthand-side argument, not the lefthand-side one. 24.2.5/6 states (about forward-iterators a and b):
If a and b are both dereferenceable, then a == b if and only if *a and *b are bound to the same object
Even though the iterators end() and cend() are not dereferenceable, the statement above implies that operator==() must be defined in such a way that the comparison is possible even if a is a const-iterator and b is not, and vice versa, because 24.2.5 is about forward-iterators in general, including both const- and non-const-versions -- this is clear e.g. from 24.2.5/1. This is why I am convinced that the wording from Table 96, which refers to convertibility, also implies comparability. But as described in cpplearner#'s later answer, this has been made explicitly clear only in C++14.
This question already has answers here:
Is comparison of const_iterator with iterator well-defined?
(3 answers)
Closed 4 years ago.
I have two iterators into a container, one const and one non-const. Is there an issue with comparing them to see if they both refer to the same object in the container? This is a general C++11 iterator question:
Can a const and non-const iterator be legitimately compared to see if
they both refer to the same object, independent of the type of
container (i.e., they are both iterators that are guaranteed to refer
to objects in the same container or that container's end(), but one is
const and the other is not)?
For example, consider the following code:
some_c++11_container container;
// Populate container
...
some_c++11_container::iterator iObject1=container.begin();
some_c++11_container::const_iterator ciObject2=container.cbegin();
// Some operations that move iObject1 and ciObject2 around the container
...
if (ciObject2==iObject1) // Is this comparison allowed by the C++11 standard?
...; //Perform some action contingent on the equality of the two iterators
Yes, this will work like you expect.
The Standard guarantees that for any container type, some_container::iterator can be implicitly converted to some_container::const_iterator.
The first table in 23.2.1 [container.requirements.general], after defining X as a container type which contains objects of type T, has:
Expression: X::iterator
Return type: iterator type whose value type is T
Note: any iterator category that meets the forward iterator requirements. convertible to X::const_iterator.
Expression: X::const_iterator
Return type: constant iterator type whose value type is T
Note: any iterator category that meets the forward iterator requirements.
(These aren't really expressions, and are types, rather than having "return types", but that's how they're squeezed into the table that is mostly expressions.)
So when you have ciObject2==iObject1, the compiler notices that the best operator== is ciObject2==some_container::const_iterator(iObject1). And operator== on two const_iterator tells you if they refer to the same element.
(I don't see anything explicitly saying that the result of this conversion refers to the same object as the original iterator. I guess that's just understood.)
From §24.2.3/1
A class or pointer type X satisfies the requirements of an input iterator for the value type T if X satisfies the Iterator (24.2.2) and EqualityComparable (Table 17) requirements ...
Thus input iterators are required to be EqualityComparable.
All standard library container iterators must satisfy forward iterator requirements (§23.2.1 - Table 96). Since those requirements are a superset of input iterator requirements, it follows these iterators must satisfy the EqualityComparable concept.
Also, from §23.2.1 - Table 96, X::iterator is required to be convertible to
X::const_iterator.
Adding the two together answers your question that it is indeed required by the standard that comparing a Container::const_iterator to a Container::iterator is well-defined (as long as both are valid iterators pointing to the same container).
I don't think it is possible for there to be an issue comparing them. If you have a const iterator when you check for it being the end the iterator end() returns is not const.
IIRC iterator implicitly converts to const_iterator. And the result must point to the same position.
If so the mixed compare will do the conversion then compare the now compatible const_iterators.
I was reading today about how for containers that support bidirectional iteration, this piece of code is valid:
Collection c(10, 10);
auto last = --c.end();
*last;
That got me thinking, is it required that when submitting a pair of bidirectional iterators [beg, end) to an algorithm in the STL that --end is defined? If so, should the result be dereferenceable?
ie
void algo(T beg, T end){
//...
auto iter = --end;
//...
*iter;
}
If the algorithm requires a range defined by bidirectional iterators first and last, then --last needs to be valid under the same conditions that ++first does -- namely that the range isn't empty. The range is empty if and only if first == last.
If the range isn't empty, then --last evaluates to an iterator that refers to the last element in the range, so *--last indeed also needs to be valid.
That said, there aren't all that many standard algorithms that require specifically a bidirectional iterator (and don't require random-access). prev, copy_backward, move_backward, reverse, reverse_copy, stable_partition, inplace_merge, [prev|next]_permutation.
If you look at what some of those do, you should see that typically the algorithm does decrement the end-of-range iterator and dereference the result.
As James says, for containers the function end() returns an iterator by value. There is no general requirement that for iterators that --x should be a well-formed expression when x is an rvalue of the type. For example, pointers are bidirectional iterators, and a function declared as int *foo(); returns a pointer by value, and --foo() is not a well-formed expression. It just so happens that for the containers you've looked at in your implementation, end() returns a class type which has operator-- defined as a member function, and so the code compiles. It also works since the container isn't empty.
Be aware that there is a difference in this respect between:
auto last = --c.end();
vs.
auto last = c.end();
--last;
The former decrements an rvalue, whereas the latter decrements an lvalue.
You read wrong. The expression --c.end() is never authorized. If the
iterator isn't at least bidirectional, it is, in fact, expressedly
forbidden, and requires a compiler error. If the collection is empty,
it is undefined behavior. And in all other cases, it will work if
it compiles, but there is no guarantee that it will compile. It failed
to compile with many early implementations of std::vector, for
example, where the iterator was just a typedef to a pointer. (In fact,
I think formally that it is undefined behavior in all cases, since
you're violating a constraint on a templated implementation. In
practice, however, you'll get what I just described.)
Arguably, because it isn't guaranteed, a good implementation will cause
it to fail to compile, systematically. For various reasons, most don't.
Don't ask me why, because it's incredibly simple to get it to fail
systematically: just make the operator-- on the iterator a free
function, rather than a member.
EDIT (additional information):
The fact that it isn't required is probably a large part of the
motivation behind std::next and std::prev in C++11. Of course,
every project I've worked on has had them anyway. The correct way to
write this is:
prev( c.end() );
And of course, the constraints that the iterator be bidirectional or
better, and that the container not be empty, still hold.
Each algorithm will tell you what type of iterator it requires. When a bidirectional iterator is called for, then naturally it will need to support decrementing.
Whether --end is possible depends on whether end == beg.
It's only required for algorithms that require bidirectional iterators.
If I swap two vectors, will their iterators remain valid, now just pointing to the "other" container, or will the iterator be invalidated?
That is, given:
using namespace std;
vector<int> x(42, 42);
vector<int> y;
vector<int>::iterator a = x.begin();
vector<int>::iterator b = x.end();
x.swap(y);
// a and b still valid? Pointing to x or y?
It seems the std mentions nothing about this:
[n3092 - 23.3.6.2]
void swap(vector<T,Allocator>& x);
Effects:
Exchanges the contents and capacity()
of *this with that of x.
Note that since I'm on VS 2005 I'm also interested in the effects of iterator debug checks etc. (_SECURE_SCL)
The behavior of swap has been clarified considerably in C++11, in large part to permit the Standard Library algorithms to use argument dependent lookup (ADL) to find swap functions for user-defined types. C++11 adds a swappable concept (C++11 §17.6.3.2[swappable.requirements]) to make this legal (and required).
The text in the C++11 language standard that addresses your question is the following text from the container requirements (§23.2.1[container.requirements.general]/8), which defines the behavior of the swap member function of a container:
Every iterator referring to an element in one container before the swap shall refer to the same element in the other container after the swap.
It is unspecified whether an iterator with value a.end() before the swap will have value b.end() after the swap.
In your example, a is guaranteed to be valid after the swap, but b is not because it is an end iterator. The reason end iterators are not guaranteed to be valid is explained in a note at §23.2.1/10:
[Note: the end() iterator does not refer to any element, so it may be
invalidated. --end note]
This is the same behavior that is defined in C++03, just substantially clarified. The original language from C++03 is at C++03 §23.1/10:
no swap() function invalidates any references, pointers, or iterators referring to the elements of the containers being swapped.
It's not immediately obvious in the original text, but the phrase "to the elements of the containers" is extremely important, because end() iterators do not point to elements.
Swapping two vectors does not invalidate the iterators, pointers, and references to its elements (C++03, 23.1.11).
Typically the iterator would contain knowledge of its container, and the swap operation maintains this for a given iterator.
In VC++ 10 the vector container is managed using this structure in <xutility>, for example:
struct _Container_proxy
{ // store head of iterator chain and back pointer
_Container_proxy()
: _Mycont(0), _Myfirstiter(0)
{ // construct from pointers
}
const _Container_base12 *_Mycont;
_Iterator_base12 *_Myfirstiter;
};
All iterators that refer to the elements of the containers remain valid
As for Visual Studio 2005, I have just tested it.
I think it should always work, as the vector::swap function even contains an explicit step to swap everything:
// vector-header
void swap(_Myt& _Right)
{ // exchange contents with _Right
if (this->_Alval == _Right._Alval)
{ // same allocator, swap control information
#if _HAS_ITERATOR_DEBUGGING
this->_Swap_all(_Right);
#endif /* _HAS_ITERATOR_DEBUGGING */
...
The iterators point to their original elements in the now-swapped vector object. (I.e. w/rg to the OP, they first pointed to elements in x, after the swap they point to elements in y.)
Note that in the n3092 draft the requirement is laid out in §23.2.1/9 :
Every iterator referring to an
element in one container before the
swap shall refer to the same element
in the other container after the swap.