Is comparison of const_iterator with iterator well-defined? - c++

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(&ast;)).
(The answer is also yes for begin() vs. cbegin() for the same reasons, as defined in the same table.)
(&ast;) 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.

Related

Strange object instantiation

In Bjarne Stroustrup's book:Tour of C++, I have found following snippet:
void test(){
string input = "aa as; asd ++easdf asdfg";
regex pat {R"(\s+(\w+))"};
for (sregex_iterator p(input.begin(),input.end(),pat); p!=sregex_iterator{}; ++p)
cout << (*p)[1] << '\n';
}
I have a problem understanding what is sregex_iterator{} doing. What kind of statement is it? Constructor? Initializer List?
It is creating a (temporary) object of type std::sregex_iterator by calling its default constructor (using uniform syntax). Since C++14, the standard requires that all types that model the ForwardIterator concept be DefaultConstructible and that a default-constructed iterator represents a past-the-end iterator.
From cppreference:
Singular iterators (since C++14)
A value-initialized ForwardIterator behaves like the past-the-end iterator of some unspecified empty container: it compares equal to all value-initialized ForwardIterators of the same type.
So the complete p!=sregex_iterator{}; statement checks whether the iterator p is not yet exhausted.
It is calling the default constructor using Uniform Initialization introduced in C++11.

Is it well-defined to compare with a value-initialized iterator?

Does the following program invoke undefined behavior?
#include <iostream>
#include <iterator>
int main(int argc, char* argv[])
{
for (auto it = std::istream_iterator<std::string>(std::cin);
it != std::istream_iterator<std::string>();
++it)
{
std::cout << *it << " ";
}
return 0;
}
This 4 year old question says that they can't be compared:
Iterators can also have singular values that are not associated with
any container. [Example: After the declaration of an uninitialized
pointer x (as with int* x;), x must always be assumed to have a
singular value of a pointer. ] Results of most expressions are
undefined for singular values; the only excep- tion is an assignment
of a non-singular value to an iterator that holds a singular value.
But another answer for says for the C++14 standard:
However, value-initialized iterators may be compared and shall compare
equal to other value-initialized iterators of the same type.
You are conflating two different issues.
istream_iterator is an input iterator, not a forward iterator, so the C++14 change you cited doesn't apply to it at all. You are allowed to compare istream_iterators in that manner because they are explicitly specified to allow such comparisons. The standard says that (§24.6.1 [istream.iterator])
The constructor with no arguments istream_iterator() always
constructs an end-of-stream input iterator object, which is the only
legitimate iterator to be used for the end condition. [...]
Two end-of-stream iterators are always equal. An end-of-stream
iterator is not equal to a non-end-of-stream iterator. Two
non-end-of-stream iterators are equal when they are constructed from
the same stream.
For forward iterators (which also includes bidirectional and random access ones) in general, value-initialized iterators are made comparable to each other in C++14. If your standard library implements it, then you can compare two value-initialized iterators. This allows you to create an empty range without an underlying container. However, you are still not allowed to compare a non-singular iterator to a value-initialized iterator. The following code has undefined behavior even in C++14:
std::list<int> l;
if(l.begin() == std::list<int>::iterator())
foo();
else
bar();

Are const iterators still evil in C++14

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)

comparing iterators from different containers

Is it legal to compare iterators from different containers?
std::vector<int> foo;
std::vector<int> bar;
Does the expression foo.begin() == bar.begin() yield false or undefined behavior?
(I am writing a custom iterator and stumbled upon this question while implementing operator==.)
If you consider the C++11 standard (n3337):
§ 24.2.1 — [iterator.requirements.general#6]
An iterator j is called reachable from an iterator i if and only if there is a finite sequence of applications of the expression ++i that makes i == j. If j is reachable from i, they refer to elements of the same sequence.
§ 24.2.5 — [forward.iterators#2]
The domain of == for forward iterators is that of iterators over the same underlying sequence.
Given that RandomAccessIterator must satisfy all requirements imposed by ForwardIterator, comparing iterators from different containers is undefined.
The LWG issue #446 talks specifically about this question, and the proposal was to add the following text to the standard (thanks to #Lightness Races in Orbit for bringing it to attention):
The result of directly or indirectly evaluating any comparison function or the binary - operator with two iterator values as arguments that were obtained from two different ranges r1 and r2 (including their past-the-end values) which are not subranges of one common range is undefined, unless explicitly described otherwise.
Undefined behavior as far as I know. In VS 2010 with
/*
* to disable iterator checking that complains that the iterators are incompatible (come from * different containers :-)
*/
#define _HAS_ITERATOR_DEBUGGING 0
std::vector<int> vec1, vec2;
std::vector<int>::iterator it1 = vec1.begin();
std::vector<int>::iterator it2 = vec2.begin();
if (it1 == it2)
{
std::cout << "they are equal!!!";
}
The equality test returns in this case true :-), since the containers are empty and the _Ptr member of the iterators are both nullptr.
Who knows maybe your implementation does things differently and the test would return false :-).
EDIT:
See C++ Standard library Active Issues list "446. Iterator equality between different containers". Maybe someone can check the standard to see if the change was adopted?
Probably not since it is on the active issues list so Charles Bailey who also answered this is right it's unspecified behavior.
So I guess the behavior could differ (at least theoretically) between different implementations and this is only one problem.
The fact that with iterator debugging enabled in the STL implementation that comes with VS checks are in place for this exact case (iterators coming from different containers) singnals at least to me once more that doing such comparisons should be avoided whenever possible.
You cannot directly compare iterators from different containers. An iterator is an object that uses the internal state of a container to traverse it; comparing the internals of one container to another simply does not make sense.
However, if the iterators resulting from container.begin() are available, it may make sense to compare iterators by the count of objects traversed from begin() to the current iterator value. This is done using std::distance:
int a = std::distance(containerA.begin(), iteratorA);
int b = std::distance(containerB.begin(), iteratorB);
if (a <comparison> b)
{ /* ... */ }
Without more context, it's difficult to judge whether this would solve your problem or not. YMMV.
No. If it were legal, this would imply that pointers would not be iterators.
I believe that it is unspecified behaviour (C++03). std::vector iterators are random access iterators and the behaviour of == is defined in the requirements for forward iterators.
== is an equivalence relation
Note that this is a requirement on a type, so must be applicable (in this case) to any pair of valid (dereferencable or otherwise) std::vector::iterators. I believe that this means == must give you a true/false answer and can't cause UB.
— If a and b are equal, then either a and b are both dereferenceable or else neither is dereferenceable.
Conversely, a dereferenceable iterator cannot compare equal to an iterator that is not dereferenceable.
— If a and b are both dereferenceable, then a == b if and only if *a and *b are the same object.
Note the lack of requirement on whether a == b for two iterators that aren't dereferenceable. So long as == is transitive (if a.end() == b.end() and b.end() == c.end() then a.end() == c.end()), reflexive (a.end() == a.end()) and symmetric (if a.end() == b.end() then b.end() == a.end()) it doesn't matter if some, all or no end() iterators to different containers compare equal.
Note, also, that this is in contrast to <. < is defined in terms of b - a, where a and b are both random access iterators. A pre-condition of performing b - a is that there must be a Distance value n such that a + n == b which requires a and b to be iterators into the same range.
ISO/IEC 14882:2003(E) 5.10.1
The == (equal to) and the != (not equal to) operators have the same semantic restrictions, conversions, and result type as the relational operators except for their lower precedence and truth-value result. [ .. ] Pointers to objects or functions of the same type (after pointer conversions) can be compared for equality. Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address (3.9.2).
Simulation Results on XCode (3.2.3):
#include <iostream>
#include <vector>
int main()
{
std::vector <int> a,aa;
std::vector <float> b;
if( a.begin() == aa.begin() )
std::cout << "\n a.begin() == aa.begin() \n" ;
a.push_back(10) ;
if( a.begin() != aa.begin() )
std::cout << "\n After push back a.begin() != aa.begin() \n" ;
// Error if( a.begin() == b.begin() )
return 0;
}
Output :
a.begin() == aa.begin()
After push back a.begin() != aa.begin()
I don't get the requirements on input iterators from the standard 100%, but from there on (forward/bidirectional/random access iterators) there are no requirements on the domain of ==, so it must return false result in an equivalence relation. You can't do < or > or subtraction on iterators from different containers though.
Edit: It does not have to return false, it has to result in an equivalence relation, this allows .begin() of two empty containers to compare equal (as shown in another answer). If the iterators are dereferencable, a == b => *a == *b has to hold. It's still not undefined behaviour.

Comparing default-constructed iterators with operator==

Does the C++ Standard say I should be able to compare two default-constructed STL iterators for equality? Are default-constructed iterators equality-comparable?
I want the following, using std::list for example:
void foo(const std::list<int>::iterator iter) {
if (iter == std::list<int>::iterator()) {
// Something
}
}
std::list<int>::iterator i;
foo(i);
What I want here is something like a NULL value for iterators, but I'm not sure if it's legal. In the STL implementation included with Visual Studio 2008, they include assertions in std::list's operator==() that preclude this usage. (They check that each iterator is "owned" by the same container and default-constructed iterators have no container.) This would hint that it's not legal, or perhaps that they're being over-zealous.
OK, I'll take a stab. The C++ Standard, Section 24.1/5:
Iterators can also have singular
values that are not associated with
any container. [Example: After the
declaration of an uninitialized
pointer x (as with int* x;), x must
always be assumed to have a singular
value of a pointer. ] Results of most
expressions are undefined for singular
values; the only excep- tion is an
assignment of a non-singular value to
an iterator that holds a singular
value.
So, no, they can't be compared.
This is going to change in C++14. [forward.iterators] 24.2.5p2 of N3936 says
However, value-initialized iterators may be compared and shall compare
equal to other value-initialized iterators of the same type.
I believe you should pass a range to the function.
void fun(std::list<int>::iterator beg, std::list<int>::iterator end)
{
while(beg != end)
{
// do what you want here.
beg++;
}
}
Specification says that the postcondition of default constructor is that iterator is singular. The comparison for equality are undefined, so it may be different in some implementation.