views:

237

answers:

4

I am having a problem using const reverse iterators on non-const containers with gcc. Well, only certain versions of gcc.

#include <vector>
#include <iostream>

using namespace std;

int main() {
    const char v0[4] = "abc";
    vector<char> v(v0, v0 + 3);

    // This block works fine
    vector<char>::const_iterator i;
    for (i = v.begin(); i != v.end(); ++i)
        cout << *i;
    cout << endl;

    // This block generates compile error with gcc 3.4.4 and gcc 4.0.1
    vector<char>::const_reverse_iterator r;
    for (r = v.rbegin(); r != v.rend(); ++r)
        cout << *r;
    cout << endl;

    return 0;
}

This program compiles OK and runs with gcc 4.2.1 (Mac Leopard) and with Visual Studio 8 and 9 (Windows), and with gcc 4.1.2 (Linux).

However, there is a compile error with gcc 3.4.4 (cygwin) and with gcc 4.0.1 (Mac Snow Leopard).

test.cpp:18: error: no match for 'operator!=' in 'r != std::vector<_Tp, _Alloc>::rend() [with _Tp = char, _Alloc = std::allocator<char>]()'

Is this a bug in earlier versions of gcc?

Due to other problems with gcc 4.2.1 on Mac, we need to use gcc 4.0.1 on Mac, so simply using the newer compiler is not a perfect solution for me. So I guess I need to change how I use reverse iterators. Any suggestions?

+2  A: 

Random things I would try:

Cast the return from rend() to a const_reverse_iterator to see if the issue is in comparing a regular to a const iterator:

r != static_cast<vector<char>::const_reverse_iterator>(v.rend())

If that doesn't work, how about changing r from a const_reverse_iterator to a regular reverse iterator.

No clue if these work, but that's what I'd try in this situation.

R Samuel Klatchko
That cast does correct the problem. It's not very pretty but it does work.
Christopher Bruns
+1  A: 

It could be a bug in the old vesion of gcc, but my guess is that the bug is in your code -- you've failed to #include <iterator>. It's probably only worth looking further if fixing that doesn't fix the problem.

On the other hand, if you're using the reverse_iterator as shown (i.e. the body of the loop is cout << *r;) you should probably just use std::copy:

std::ostream_iterator<char> output(std::cout);

// frontwards
std::copy(v.begin(), v.end(), output);

// backwards
std::copy(v.rbegin(), v.rend(), output);

There is also a copy_backwards, but I don't believe it'll do what you want.

Edit: One other possibility to consider: if adding the required header doesn't work, and you really need the reverse iterator, you might consider using STLPort in place of the native library (at least for the older compilers).

Jerry Coffin
Adding "#include <iterator>" does not change the behavior.
Christopher Bruns
+7  A: 

It is a defect in the current standard: http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#280

Edit: Elaborating a bit: The issue is that, in the current standard:

  • vector::reverse_iterator is specified as std::reverse_iterator<vector::iterator>, and vector::const_reverse_iterator as std::reverse_iterator<vector::const_iterator>.
  • The relational operators on std::reverse_iterator are defined with a single template parameter, making reverse_iterator<iterator> and reverse_iterator<const_iterator> non-comparable.

In your code, you compare a const_reverse_iterator with the result of calling "rend()" on a non-const vector, which is a (non-const) reverse_iterator.

In C++0x, two related changes are made to fix issues like this:

  • Relational operators on reverse_iterator now take two template parameters
  • Containers like vector have extra methods to explicitely request a const_iterator: cbegin(), cend(), crbegin() and crend().

In your case, a workaround would be to explicitely request the const_reverse_iterator for rend():

vector<char>::const_reverse_iterator r;
const vector<char>::const_reverse_iterator crend = v.rend();
for (r = v.rbegin(); r != crend; ++r)
    cout << *r;
Éric Malenfant
Nice answer. It is more a bug in the standard than a bug in gcc.
Christopher Bruns
@Christopher Bruns: I guess that making crend non-const, like r, would make things work.
Éric Malenfant
@Eric Malenfant: re your fist bullet point: I think const_reverse_iterator is defined as "reverse_iterator<const_iterator>", not just "const_iterator"
Christopher Bruns
@Christopher Bruns: True. A case of "missing edit after copy and paste" error. Fixed and thanks for mentioning.
Éric Malenfant
+1  A: 

Since the iterators need to be the same type to be comparable, and a non-const container yields non-const iterators, why not declare and initialize the end iterator at the same time.

for (vector<char>::const_reverse_iterator r = v.rbegin(), end_it = v.rend(); r != end_it; ++r)
    cout << *r;

With an older compiler this might even give a small performance benefit.

UncleBens