36

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?

Ruslan
  • 18,162
  • 8
  • 67
  • 136

3 Answers3

43

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.

cpplearner
  • 13,776
  • 2
  • 47
  • 72
  • That only means that const and non const iterators that comes from a same object matching the `Container` concept can be safely compared. But you cannot make that assumption for other iterators. – ABu Apr 07 '17 at 19:45
11

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.

jogojapan
  • 68,383
  • 11
  • 101
  • 131
10

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.

Community
  • 1
  • 1
Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
  • 3
    _Convertible_ doesn't imply _comparable_. – cpplearner Feb 14 '16 at 11:31
  • 4
    For instance, convertible does not rule out comparison operators implemented as member functions, meaning `i < ci` resolves to `i.operator <(ci)`, where no conversion of `i` gets considered. There may be additional guarantees in the standard, but if so, they should be in the answer. –  Feb 14 '16 at 11:43
  • @hvd: fortunately, in the questioner's code, the const_iterator is on the LHS of the comparison, and the plain iterator is on the right. So it's converted. Or I guess we could say it's unfortunate, since it means the compiler doesn't catch the unintended use of `end()`. – Steve Jessop Feb 14 '16 at 16:28
  • 1
    @SteveJessop It was just an example. Another is operators that do not take `const_iterator` at all, but simply something that `const_iterator` is implicitly convertible to, requiring two user-defined conversions. A third is a template comparison operator where the type argument cannot be deduced because of a `const_iterator`/`iterator` mismatch. (Luckily there is an answer now that shows the additional requirements in the standard.) –  Feb 14 '16 at 16:37